/**
 *  Copyright © 2025, Luis Andrés Lange <https://javacomm.net>
 *
 *  Previously released under Apache License 2.0; now licensed under MPL 2.0.
 *
 *  This Source Code Form is subject to the terms of the Mozilla Public
 *  License, v. 2.0. If a copy of the MPL was not distributed with this
 *  file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 *  ----------------------------------------------------------------------------
 *
 *  Exhibit B - "Incompatible With Secondary Licenses" Notice
 *
 *  This Source Code Form is "Incompatible With Secondary Licenses",
 *  as defined by the Mozilla Public License, v. 2.0.
 *
 *  In short:
 *  - This file may be used, modified, and distributed under MPL 2.0 only.
 *  - It may NOT be relicensed under GPL, LGPL, AGPL, or any other Secondary License.
 *
 *  Rationale:
 *  - Ensures that the code remains MPL-2.0.
 *  - Avoids legal conflicts with GPL-licensed libraries (e.g., VideoLAN).
 *  - Maximizes usability for commercial and security-critical applications.
 *
 */
package net.javacomm.facade;

import static net.javacomm.protocol.HEADER.CONFIRM;
import static net.javacomm.protocol.HEADER.ERROR;
import static net.javacomm.protocol.HEADER.REQUEST;
import static net.javacomm.protocol.HEADER.RESPONSE;
import jakarta.websocket.EncodeException;
import jakarta.xml.bind.JAXBException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.TreeMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.SecretKey;
import javax.naming.NamingException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tomcat.jdbc.pool.ConnectionPool;
import org.apache.tomcat.jdbc.pool.PoolProperties;
import org.nexuswob.util.Util;
import org.xml.sax.SAXException;
import net.javacomm.database.DatabaseException;
import net.javacomm.database.Persistence;
import net.javacomm.database.PoolException;
import net.javacomm.database.entity.ChatfileChunk;
import net.javacomm.database.entity.Config;
import net.javacomm.database.entity.EntityUser;
import net.javacomm.database.entity.FiletransferConfig;
import net.javacomm.database.entity.Iptv;
import net.javacomm.database.entity.Konferenzraum;
import net.javacomm.database.entity.Online;
import net.javacomm.database.entity.Outdated;
import net.javacomm.database.entity.PrivateChatfile;
import net.javacomm.database.entity.Record;
import net.javacomm.database.entity.Screencast;
import net.javacomm.database.entity.Telko;
import net.javacomm.database.entity.UserModule;
import net.javacomm.database.entity.UserOdx;
import net.javacomm.database.entity.ViewSenderSession;
import net.javacomm.multilingual.MultilingualString;
import net.javacomm.multilingual.schema.ISO639;
import net.javacomm.multilingual.schema.KEY;
import net.javacomm.protocol.Agent;
import net.javacomm.protocol.Attachment;
import net.javacomm.protocol.AttachmentImpl;
import net.javacomm.protocol.Benutzerstatus;
import net.javacomm.protocol.BlacklistTypes;
import net.javacomm.protocol.CALLPRIVATECHAT;
import net.javacomm.protocol.CALLREMOTEUSER;
import net.javacomm.protocol.CANDIDATETOPICMEMBER;
import net.javacomm.protocol.CHANGETOPICMEMBER;
import net.javacomm.protocol.CHATONLINELIST;
import net.javacomm.protocol.CHATUSERLIST;
import net.javacomm.protocol.CONFERENCE;
import net.javacomm.protocol.CREATETEMPROOM;
import net.javacomm.protocol.ChatOnline;
import net.javacomm.protocol.ChatUser;
import net.javacomm.protocol.Command;
import net.javacomm.protocol.DELETEUSER;
import net.javacomm.protocol.DIAL;
import net.javacomm.protocol.DOWNLOAD;
import net.javacomm.protocol.ENTERROOM;
import net.javacomm.protocol.Entry;
import net.javacomm.protocol.HISTORYMESSAGE;
import net.javacomm.protocol.KonferenzraumUser;
import net.javacomm.protocol.Language;
import net.javacomm.protocol.Lifetime;
import net.javacomm.protocol.MessageEncoder;
import net.javacomm.protocol.Protocol;
import net.javacomm.protocol.READTOPICROOMOWNER;
import net.javacomm.protocol.ROOMLIST;
import net.javacomm.protocol.RecordImpl;
import net.javacomm.protocol.Room;
import net.javacomm.protocol.Roomtype;
import net.javacomm.protocol.SIGNIN;
import net.javacomm.protocol.Searchfile;
import net.javacomm.protocol.TOPICMEMBER;
import net.javacomm.protocol.Token;
import net.javacomm.protocol.UPDATEUSER;
import net.javacomm.protocol.USERONLINELIST;
import net.javacomm.protocol.USRLOGIN;
import net.javacomm.protocol.Uploadfile;
import net.javacomm.protocol.UserOnline;
import net.javacomm.protocol.crypto.Crypto;
import net.javacomm.schulz.Functions;
import net.javacomm.share.Constants;
import net.javacomm.transfer.TransferKonferenzraum;
import net.javacomm.transfer.TransferOdxModulPermission;
import at.favre.lib.crypto.bcrypt.BCrypt;



public abstract class DatabaseService {

  final static Pattern PATH_SEPARATOR = Pattern.compile(".*[\\x5c/](.+)$");
  public final static Pattern APODO = Pattern
      .compile("[\\p{Alnum}äöüÄÖÜßáéíóúÁÉÍÓÚñÑ &-._]{1," + String.valueOf(Constants.LEN_NICKNAME) + "}");
  final static int MAX_USER_ONLINE = 256;
  public final static Pattern EOT4 = Pattern.compile("[\\p{Space}\\x04]*");

  public final static String TIMEZONE = "Europe/Berlin";
  private final Logger log = LogManager.getLogger(DatabaseService.class);
  protected ConnectionPool pool;
  protected AtomicInteger transferlibNumber = new AtomicInteger(0);
  protected Persistence persistence;
  private final String ENTRY = "ENTRY";
  private final String LIFETIME = "LIFETIME";
  private final String CHATID = "CHATID";
  private final String BACKGROUND = "BACKGROUND";
  private final String SESSION = "SESSION";
  private final String ROOMTYPE = "ROOMTYPE";
  private final String SUFFIX = "SUFFIX";
  private final String FOREGROUND = "FOREGROUND";
  private final String MAIL = "MAIL";
  private final String NICKNAME = "NICKNAME";
  private final String UID = "UID";
  private final String MODULE = "MODULE";
  private final String TIME = "TIME";
  private final String LDELETE = "LDELETE";
  private final String FILESIZE = "FILESIZE";
  private final String SENDER = "SENDER";
  private final String BENUTZERSTATUS = "BENUTZERSTATUS";
  private final String CONFIRMATION_FLAG = "CONFIRMATION_FLAG";
  private final String LANGUAGE = "LANGUAGE";
  private final String KEEPALIVE = "KEEPALIVE";
  private final String AES = "AES";
  private final String SINCE = "SINCE";
  private final String AGENT = "AGENT";
  private final String ONBUSY = "ONBUSY";
  private final String ORGANISATOR = "ORGANISATOR";
  private final String KONFERENZRAUM = "KONFERENZRAUM";

  public DatabaseService() {
    persistence = Persistence.getInstance();
  }



  /**
   * Die Filterliste für alle verbotenen Raumnamen wird aktualisiert.
   *
   *
   * @param filterliste
   *                    eine Filterliste, die alle verbotenen Namen enthält
   *
   * @return {@code true}, die Filterliste konnte gespeichert werden
   */
  public boolean addRoomfilter(List<String> filterliste) {
    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_INSERT_FORBIDDEN_ROOM);
    logSQLKey(Persistence.KEY_INSERT_FORBIDDEN_ROOM, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      for (String value : filterliste) {
        prepStatement.setString(1, value);
        prepStatement.addBatch();
      }
      prepStatement.executeBatch();
      done = true;
    }
    catch (SQLException e) {
      throw new DatabaseException(e.getMessage());
    }
    return done;

  }



  /**
   * Wenn der Teilnehmer in einer Konferenz ist, dann kann er nicht von einem
   * Dritten angerufen werden.
   *
   *
   * @param userid
   *               dieser User wird angerufen
   * @return {@code true}, der Anrufer wird nicht durchgestellt
   */
  public boolean blockConferenceCall(String userid) {
    boolean done = true;

    String sql = persistence.getSQL(Persistence.KEY_BLOCK_CONFERENCE_CALL);
    logSQLKey(Persistence.KEY_BLOCK_CONFERENCE_CALL, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepareStatement = connection.prepareStatement(sql)
    ) {
      prepareStatement.setString(1, userid);
      try(ResultSet rs = prepareStatement.executeQuery()) {
        done = rs.next();
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return done;
  }



  /**
   *
   * @param request
   * @param sessionid
   *                  die Sessionid vom Anfrager
   * @return
   */
  public CALLREMOTEUSER callRemoteuser(CALLPRIVATECHAT request, String sessionid) {
    CALLREMOTEUSER remoteuser = new CALLREMOTEUSER();
    remoteuser.setCommand(Command.CALLREMOTEUSER);
    remoteuser.setHeader(REQUEST);
    remoteuser.setDataset(Protocol.DATASET);

    String remoteNickname = request.getRemoteNickname();
    // sprich den remoteUser an

    // REmouteNickname, RemoteSession, RemoteUserID
    // SQL select t_online.uid,t_online.session,nickname from t_online,t_user
    // where t_online.uid=t_user.uid and nickname='hallo';
    // select t_online.uid,t_online.session,nickname from t_online,t_user where
    // t_online.uid=t_user.uid and nickname='hallo';
    String sql = persistence.getSQL(Persistence.KEY_CALL_REMOTEUSER);
    logSQLKey(Persistence.KEY_CALL_REMOTEUSER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setString(1, remoteNickname);
      try(ResultSet rs = statement.executeQuery();) {
        if (rs.next()) {
          // String remoteUID = rs.getString("UID");
          String remoteSession = rs.getString(SESSION);
          remoteNickname = rs.getString(NICKNAME);
          remoteuser.setLocalSessionid(remoteSession);
          remoteuser.setLocalNickname(remoteNickname);
          remoteuser.setRemoteNickname(request.getLocalNickname());
          remoteuser.setRemoteSessionid(sessionid); // diesen Eintrag muss ich
                                                    // noch erfragen
        }
        else {
          log.info("Der RemoteUser ist nicht vorhanden");
          remoteuser = new CALLREMOTEUSER();
          remoteuser.setCommand(Command.CALLREMOTEUSER);
          remoteuser.setHeader(ERROR);
          remoteuser.setDataset(Protocol.DATASET);
          remoteuser.setErrorMessage("Dein Gesprächspartner ist nicht erreichbar.");
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return remoteuser;
  }



  /**
   * Einem Raum werden Mitglieder zugewiesen. Der Raum muss ein STAMMTISCH oder
   * ein PAUSENRAUM sein.
   *
   *
   * @param userid
   *                 UserID
   * @param room
   *                 ein Raum
   * @param roomtype
   *                 STAMMTISCH oder PAUSENRAUM
   * @return
   */
  public CHANGETOPICMEMBER changeMitglieder(String userid, String room, Roomtype roomtype,
      ChatUser[] chatuser) {

    CHANGETOPICMEMBER response = new CHANGETOPICMEMBER();

    // ist der raum vorhanden?, wenn nicht ERROR zurücksenden

    String sql = persistence.getSQL(Persistence.KEY_CHANGE_TOPICMEMBER);
    String isExists = persistence.getSQL(Persistence.KEY_IS_ROOM_EXISTS);
    String fetchUser = persistence.getSQL(Persistence.KEY_FETCHUSER);
    String sqlKEY_GROUP_USER = persistence.getSQL(Persistence.KEY_GROUP_USER);
    logSQLKey(Persistence.KEY_IS_ROOM_EXISTS, isExists);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepIsExists = connection.prepareStatement(isExists);
      PreparedStatement prepKEY_USERNICKNAME = connection.prepareStatement(fetchUser);
      PreparedStatement prepstatement2 = connection.prepareStatement(sqlKEY_GROUP_USER);
      PreparedStatement prepstatement = connection.prepareStatement(sql);
    ) {
      // prepIsExists.setString(1, request.getRoom());
      prepIsExists.setString(1, room);
      log.debug("room=" + room);

      try(ResultSet rs = prepIsExists.executeQuery()) {
        if (!rs.next()) {
          response.setCommand(Command.CHANGETOPICMEMBER);
          response.setHeader(ERROR);
          response.setDataset(Protocol.DATASET);
          response.setUserid(userid);
          response.setRoom(room);
          StringBuilder buffer = new StringBuilder();
          buffer.append("Der Besprechungsraum <b><span style=\"color:red\"> ");
          buffer.append(room);
          buffer.append("</b></span> ist nicht vorhanden.");
          response.setErrorMessage(buffer.toString());
          return response;
        }
        String tb_chat_groupid = rs.getString("GROUPID");
        log.info("groupid=" + tb_chat_groupid); // Der vorliegende Raum gehört
                                                // zu Gruppe groupid aus TB_CHAT

        // Hole alle (User/Nickname) Tupel einer Gruppe.
        logSQLKey(Persistence.KEY_FETCHUSER, fetchUser);
        int count = chatuser.length;

        // Wie viele User gehören zum Chat?
        // Bei einem private sind es zu Beginn natürlich keiner

        for (int index = 0; index < count; index++) {
          prepKEY_USERNICKNAME.setString(1, chatuser[index].getNickname());
          log.info(chatuser[index].getNickname());
          try(ResultSet rsUserid = prepKEY_USERNICKNAME.executeQuery()) {
            if (rsUserid.next()) {
              chatuser[index].setUserid(rsUserid.getString("UID"));
            }
          }
        }

        // KEY_CHANGE_TOPIC_MEMBER ist ein Löschbefehl
        // Bei einem Pausenraum, FORUM nichts löschen

        switch(roomtype) {
          case BESPRECHUNGSRAUM:
            logSQLKey(Persistence.KEY_CHANGE_TOPICMEMBER, sql);
            prepstatement.setString(1, room);
            prepstatement.setString(2, userid);
            prepstatement.executeUpdate();
            break;
          case PAUSENRAUM:
            break;
          case FORUM:
            break;
          case GRUPPENRAUM:
            break;
          default:
            break;
        }

        // Schleife von inserts
        // Ein USER wird in eine Gruppe aufgenommen.
        logSQLKey(Persistence.KEY_GROUP_USER, sqlKEY_GROUP_USER);
        for (int index = 0; index < count; index++) {
          log.info(tb_chat_groupid + "/" + chatuser[index].getUserid());
          try {
            prepstatement2.clearParameters();
            prepstatement2.setString(1, tb_chat_groupid); // GROUPID
            prepstatement2.setString(2, chatuser[index].getUserid()); // userid
            prepstatement2.executeUpdate();
          }
          catch (SQLException e) {
            log.warn(tb_chat_groupid + "/" + chatuser[index].getUserid());
            log.warn(e.getMessage());
            log.warn("alles ok, der Fehler wurde abgefangen - KEY_GROUP_USER");
            log.warn("--");
            // ein duplicate Entry
            // kann passieren, wenn zuvor ein anderer Anwender den User
            // übernommen hat
            // parallele Bearbeitung
          }
        }

        response.setCommand(Command.CHANGETOPICMEMBER);
        response.setHeader(RESPONSE);
        response.setDataset(Protocol.DATASET);
        response.setUserid(userid);
        response.setRoom(room);
        response.setRoomtype(roomtype);
        response.setChatUser(chatuser);

      }

    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return response;
  }



  /**
   * NEUES_PASSWORD wird nach PASSWORD kopiert.
   *
   * @param userid
   *               die UserID
   *
   * @return {@code true}, der Kopiervorgang war erfolgreich
   */
  public boolean changePassword(String userid, String confirmationKey) {
    boolean done = false;
    String sqlPassword = persistence.getSQL(Persistence.KEY_UPDATE_PASSWORD);
    String sql = persistence.getSQL(Persistence.KEY_SELECT_NEUES_PASSWORD);
    logSQLKey(Persistence.KEY_SELECT_NEUES_PASSWORD, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql);
      PreparedStatement prepstatementPassword = connection.prepareStatement(sqlPassword);
    ) {
      prepstatement.setString(1, userid);
      try(ResultSet rs = prepstatement.executeQuery();) {
        if (rs.next()) {
          String neues_password = rs.getString(1);
          // neues Passwort ist ein MD5 Wert
          // update auf neues Password
          logSQLKey(Persistence.KEY_UPDATE_PASSWORD, sqlPassword);
          prepstatementPassword.setString(1, neues_password);
          prepstatementPassword.setString(2, userid);
          prepstatementPassword.setString(3, confirmationKey);
          int result = prepstatementPassword.executeUpdate();
          done = result == 1;
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return done;
  }



  /**
   * Wandel nach n-Minuten einen öffentlichen Raum in ein Forum um.
   *
   * @param minutos
   *                Minuten
   */
  public int changeToForum(int minutos) {
    int result = 0;
    String sql = persistence.getSQL(Persistence.KEY_CHANGE_TO_FORUM);
//    logSQLKey(Persistence.KEY_CHANGE_TO_FORUM, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {

      statement.setInt(1, minutos);
      result = statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return result;
  }



  /**
   * In wie vielen Chaträumen ist ein Anwender online? Mitgezählt werden Private
   * Chats.
   * 
   * @param userid
   *               dieser Anwender
   * 
   * @return alle Chaträume, TB_PRIVATE_CHAT + TB_USER_CHAT
   * 
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht verarbeitet werden
   */
  public int chatCounts(String userid) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_COUNT_CHATS);
    logSQLKey(Persistence.KEY_COUNT_CHATS, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepareStatement = connection.prepareStatement(sql)
    ) {
      prepareStatement.setString(1, userid);
      prepareStatement.setString(2, userid);
      try(ResultSet rs = prepareStatement.executeQuery()) {
        if (rs.next()) {
          return rs.getInt(1);
        }
        throw new DatabaseException("die Anzahl der Chaträume konnte nicht berechnet werden.");
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Alle Tabellen mit einer 1:1 Beziehung zur TB_USER werden geprüft, ob sie eine
   * UID enthalten, die in der TB_USER vorkommt. Falls die UID in der TB_XYZ
   * fehlt, wird die fehlende UID in TB_XYZ eingefügt. Betroffen von dieser
   * Vorgehensweise sind Konfigurationstabellen.
   *
   */
  public void checkConfigs() {
    ArrayList<String> uids = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.CHECK_UID_FTS);
    String insert_fts_config = persistence.getSQL(Persistence.INSERT_FTS_CONFIG);
    logSQLKey(Persistence.CHECK_UID_FTS, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql);
      PreparedStatement prepstatement = connection.prepareStatement(insert_fts_config);
    ) {

      while (rs.next()) {
        uids.add(rs.getString(1));
      }
      if (uids.size() == 0) return;
      uids.forEach(uid -> {
        // inserts
        logSQLKey(Persistence.INSERT_FTS_CONFIG, insert_fts_config);
        try {
          prepstatement.setString(1, uid);
          prepstatement.executeUpdate();
        }
        catch (SQLException e) {
          log.error(e.getMessage(), e);
        }

      });

    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);

    }

  }



  /**
   * Der Chat wird abgelehnt, wenn das Chataufkommen beim Empfänger erschöpft ist.
   * 
   * @param remoteNickname
   *                       dieser Empfängernickname
   * @param callerUserid
   *                       dieser Anwender versucht einen Private Chat zu
   *                       initiieren
   * 
   * @return {@code HEADER.ERROR}, wenn das Kontingent erschöpft ist; sonst
   *         {@code HEADER.CONFIRM}
   * 
   * @throws DatabaseException
   *                           einer oder mehrere SQL-Befehle konnten nicht
   *                           verarbeitet werden
   */
  public CALLPRIVATECHAT checkExhausted(String remoteNickname, String callerUserid) throws DatabaseException {
    String remoteUserid = fetchUserByNickname(remoteNickname);
    Agent agent = Agent.toAgent(fetchUserAgent(remoteUserid));
    // wie viele Chats hat der Empfänger offen?
    if (agent != Agent.Desktop && chatCounts(remoteUserid) >= 1) {
      String isocode = fetchLanguage(callerUserid);
      CALLPRIVATECHAT call = new CALLPRIVATECHAT();
      call.setCommand(Command.CALLPRIVATECHAT);
      call.setHeader(ERROR);
      call.setDataset(Protocol.DATASET);
      call.setMultilingualkey(KEY.SERVER_CHAT_EXHAUSTED);
      MultilingualString exhausted = new MultilingualString(
          KEY.SERVER_CHAT_EXHAUSTED, ISO639.fromValue(isocode)
      );
      call.setErrorMessage(remoteNickname + exhausted.toString());
      return call;
    }
    CALLPRIVATECHAT call = new CALLPRIVATECHAT();
    call.setCommand(Command.CALLPRIVATECHAT);
    call.setHeader(CONFIRM);
    call.setDataset(Protocol.DATASET);
    return call;
  }



  /**
   * Eine Passwortprüfung wird vorgenommen.
   *
   *
   * @param uid
   *                 eine Userid
   * @param password
   *                 das zu prüfende Passwort
   * @return {@code true}, bestätigt
   */
  public boolean checkPassword(String uid, String password) {
    if (uid == null) return false;
    if (password == null) return false;
    String sql = persistence.getSQL(Persistence.KEY_CHECK_PASSWORD);
    logSQLKey(Persistence.KEY_CHECK_PASSWORD, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setString(1, uid);
      statement.setString(2, password);
      try(ResultSet rs = statement.executeQuery()) {
        return rs.next();
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      return false;
    }
  }



  /**
   * Die Nachrichten aus dem Stack werden in einem Array abgelegt. Jede abgelegte
   * Nachricht wird aus dem Stack entfernt. Die Nachricht mit dem höchsten Index
   * ist die aktuellste Nachrichricht.
   * {@code Record[0]<Record[1]<Record[2]... Record[2]} ist die aktuelle
   * Nachricht.
   *
   * @param CHATROOM
   *                 ein Chatraum
   * @param stack
   *                 alle Nachrichten im Chatraum
   * @return die Nachrichten aus dem Stack
   */
  net.javacomm.protocol.RecordInterface[] chunk(Stack<net.javacomm.protocol.RecordInterface> stack) {
    final int max = 32; // 32 Nachrichten in einer Historymessage
    int count = Math.min(stack.size(), max);
    net.javacomm.protocol.RecordInterface[] dataset = new net.javacomm.protocol.RecordInterface[count];
    for (int index = 0; index < count; index++) {
      dataset[index] = new RecordImpl();
      dataset[index] = stack.pop();
    }
    return dataset;
  }



  /**
   * Die Tabelle TB_ROOMFILTER wird geleert.
   *
   */
  public void clearForbiddenRooms() {
    String sql = persistence.getSQL(Persistence.KEY_CLEAR_FORBIDDEN_ROOM);
    logSQLKey(Persistence.KEY_CLEAR_FORBIDDEN_ROOM, sql);
    try(
      Connection connection = pool.getConnection();
      Statement deleteROOM = connection.createStatement()
    ) {
      deleteROOM.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  public abstract void clearGroupUser();

  /**
   * Eine Datenbankverbindung wird mit den Defaultwerten aus der
   * Konfigurationsdatei <b><i>connection.xml</i></b> hergestellt.
   * 
   * @throws SAXException
   *                       die Verbindungsdaten können nicht verarbeitet werden
   * @throws JAXBException
   *                       die Verbindungsdaten können nicht verarbeitet werden
   * @throws PoolException
   *                       der Verbindungspool für die Connections konnte nicht
   *                       eingerichtet werden
   */
  public abstract void connect() throws SAXException, JAXBException, PoolException;

  /**
   * Diese Datenbankverbindung wird aufgebaut. Alle Verbindungsparameter sind in
   * den {@code PoolProperties} enthalten.
   * 
   * @param props
   * @throws PoolException
   */
  public abstract void connect(PoolProperties props) throws PoolException;



  /**
   * Wie viele Pausenräume gibt es?
   * 
   * @return
   * @throws SQLException
   */
  public int countBreakrooms() throws SQLException {
    String sql = persistence.getSQL(Persistence.KEY_COUNT_BREAKROOMS);
    logSQLKey(Persistence.KEY_COUNT_BREAKROOMS, sql);
    int count = 0;
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
    ) {
      try(ResultSet rs = statement.executeQuery(sql)) {
        if (rs.next()) {
          count = rs.getInt(1);
        }
      }
    }
    return count;
  }



  /**
   * Ermittle die Anzahl der User in einem Pausenraum oder Gruppenraum.
   *
   * @param room
   *             der Raumname
   * 
   * @return so viele Anwender sind im Pausenraum
   */
  public int countTemproomUser(String room) {
    int count = 0;
    String sql = persistence.getSQL(Persistence.KEY_COUNT_TEMPROOM_USER);
    logSQLKey(Persistence.KEY_COUNT_TEMPROOM_USER, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {

      statement.setString(1, room);
      try(ResultSet rs = statement.executeQuery()) {
        if (rs.next()) {
          count = rs.getInt(1);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return count;
  }



  /**
   * Kommt der Sender genau 1x in der TB_VIDEO vor?
   * 
   * @param userid
   *               dieser User ist ein Videosender
   * 
   * @return {@code true}, kommt genau 1x vor
   */
  public boolean countVideosender(String userid) {
    String sql = persistence.getSQL(Persistence.KEY_COUNT_VIDEOSENDER);
    logSQLKey(Persistence.KEY_COUNT_VIDEOSENDER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {
      prepstatement.setString(1, userid);
      try(ResultSet rs = prepstatement.executeQuery()) {
        if (rs.next()) {
          return rs.getInt(1) == 1;
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
    return false;
  }



  /**
   * An wie viele Entitäten sendet der User? Eine Telko mit n-Personen ist eine
   * Entität oder eine Einzelperson bei einer 1:1 Übertragung.
   * 
   * @param userid
   *               dieser User sendet Bildschirmdaten
   * 
   * @return Anzahl der Entitäten
   * @throws SQLException
   *                      der SQL-Befehl konnte nicht ausgeführt werden
   * 
   */
  public int countVideoSender(String userid) throws SQLException {
    int count = 0;
    String sql = persistence.getSQL(Persistence.KEY_COUNT_VIDEO_SENDER);
    logSQLKey(Persistence.KEY_COUNT_VIDEO_SENDER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, userid);
      try(ResultSet rs = statement.executeQuery()) {
        rs.next();
        count = rs.getInt(1);
      }
    }
    return count;
  }



  /**
   * Eine Passwortanforderung über SIGNIN | RESPONSE. Ein einmaliger
   * Bestätigungsschlüssel wird erzeugt und ein neues Password, welches vom
   * Anwender bestätigt werden muss.
   *
   *
   * @param uid
   *            die UserID
   * @return der Rückgabewert enthält die userid, confirmation_key und
   *         neues_password.
   */
  public SIGNIN createConfirmationKey(String uid) {
    SIGNIN response = new SIGNIN();
    String sql = persistence.getSQL(Persistence.KEY_CREATE_CONFIRMATION_KEY);
    logSQLKey(Persistence.KEY_CREATE_CONFIRMATION_KEY, sql);
    String neues_password = Util.getRandomID();
    String confirmation_key = Util.getRandomID();
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {

      statement.setString(1, BCrypt.withDefaults().hashToString(12, neues_password.toCharArray()));
      statement.setString(2, confirmation_key);
      statement.setString(3, uid);
      statement.executeUpdate();
      response.setHeader(RESPONSE);
      response.setCommand(Command.SIGNIN);
      response.setDataset(Protocol.DATASET);
      response.setUserid(uid);
      response.setConfirmationKey(confirmation_key);
      response.setNeuesPassword(neues_password);
      response.setMultilingualkey(KEY.SERVER_NEUES_PASSWORT);
      return response;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Creates a short (64 bit) Universal Unique Identifier.
   *
   * @return this universally unique identifier
   * 
   */
  public long createIdentifier() throws DatabaseException {
    long identifier = 0;
    String sql = persistence.getSQL(Persistence.KEY_MAX_ANLAGE);
    logSQLKey(Persistence.KEY_MAX_ANLAGE, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql)
    ) {

      rs.next();
      identifier = rs.getLong(1);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return identifier;
  }



  /**
   * Ein Nickname mit n-Zeichen wird erzeugt.
   *
   *
   * @param count
   *              Anzahl der Zeichen
   * @return der Nickname
   */
  public String createNickname(int count) {
    if (count <= 0 || count > Constants.LEN_NICKNAME) {
      throw new IllegalArgumentException(
          "count liegt nicht im Bereich 1 <= count <= " + Constants.LEN_NICKNAME
      );
    }

    final String chars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    StringBuilder buffer = new StringBuilder(count);

    for (int i = 0; i < count; i++) {
      int value = ThreadLocalRandom.current().nextInt(chars.length());
      buffer.append(chars.charAt(value));
    }

    return buffer.toString();
  }



  /**
   * Für einen vorhandenen Anwender wird ein neues Passwort erzeugt. Das Passwort
   * wird unverschlüsselt zurückgegeben aber in der Datenbank verschlüsselt
   * hinterlegt.
   *
   *
   * @param userid
   *               eine Userid
   * @return alle Anwenderdaten
   */
  public EntityUser createPassword(String userid) {
    String password = Util.getRandomID(); // Dieses Password ist neu und muss erneut in der Datenbank als MD5
                                          // Wert
                                          // abgelegt werden
    EntityUser user = fetchUser(userid);
    if (user == null) {
      log.error(userid + " - Der Anwender konnte nicht gefunden werden.");
      return new EntityUser();
    }
    user.setPassword(password);
    String md5 = BCrypt.withDefaults().hashToString(12, password.toCharArray());
    updateConfirmPassword(md5, userid, user.getMail());
    return user;
  }



  /**
   *
   * Ein Chatraum wird erstellt. Der Raumname wird gegen den Filter in
   * TB_ROOMFILTER geprüft.
   *
   * @param request
   *                Protocol mit REQUEST
   * @return Protocol mit RESPONSE oder ERROR
   */
  public CREATETEMPROOM createRoom(CREATETEMPROOM request) {
    return createRoom(request, false);
  }



  /**
   *
   * Ein Chatraum wird erstellt. Der Raumname wird gegen den Filter in
   * TB_ROOMFILTER geprüft.
   *
   * @param request
   *                Protocol mit REQUEST
   * @param test
   *                {@code true}, ein Test
   * @return Protocol mit RESPONSE oder ERROR
   */
  CREATETEMPROOM createRoom(CREATETEMPROOM request, boolean test) {
    String sql = persistence.getSQL(Persistence.KEY_CREATE_ROOM);
    CREATETEMPROOM response = new CREATETEMPROOM();
    if (test) {
      // immer anlegen
      return createTestRoom(request);
    }
    Matcher matcher = Util.ROOMNAME_PATTERN.matcher(request.getRoom());
    if (!matcher.matches()) {
      response.setCommand(Command.CREATETEMPROOM);
      response.setHeader(ERROR);
      response.setDataset(Protocol.DATASET);
      response.setRoom(request.getRoom());
      response.setUser(request.getUser());
      response.setRoomtype(request.getRoomtype());
      response.setMultilingualkey(
          request.getRoom().length() <= 1 ? KEY.SERVER_ZU_KURZER_RAUMNAME
              : KEY.SERVER_DER_CHATNAME_IST_NICHT_ERLAUBT
      );
      return response;
    }
    // Schulz Functions; ein Raumname wird gegen eine Verbotsliste
    // geprüft.
    List<String> verbostliste = fetchForbiddenNames();
    String result = request.getRoom().replaceAll("\\d", "").replaceAll("\\p{Punct}", "")
        .replaceAll("\\p{Space}", "");
    for (String verboten : verbostliste) {
      if (Functions.isStringInString(verboten, result)) {
        response.setCommand(Command.CREATETEMPROOM);
        response.setHeader(ERROR);
        response.setDataset(Protocol.DATASET);
        response.setRoom(request.getRoom());
        response.setUser(request.getUser());
        response.setRoomtype(request.getRoomtype());
        response.setMultilingualkey(KEY.SERVER_DER_CHATNAME_IST_NICHT_ERLAUBT);

        // Ich springe aus einer Schleife. Darauf steht die Todesstrafe in der
        // Informatik.
        return response;
      }
    }

    logSQLKey(Persistence.KEY_CREATE_ROOM, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql);
    ) {

      prepstatement.clearParameters();
      prepstatement.setString(1, request.getRoom());

      // PUBLIC-ROOM hat keine GROUP
      switch(request.getEntry()) {
        case PUBLIC:
          prepstatement.setString(2, null);
          break;
        case PROTECTED:
          // NEU 210205
          // Die Gruppe heisst wie der Raum.
          // Eine Gruppe muss vorhanden sein, sonst ist eine Anmeldung nicht
          // möglich
          // Die Gruppe ist nicht an einen Anwender gebunden
          prepstatement.setString(2, request.getRoom());
          break;
      }
      logSQLKey(Persistence.KEY_CREATE_ROOM, sql);
      prepstatement.setInt(3, request.getLifetime().getLifetimeNumber());
      prepstatement.setInt(4, request.getEntry().getEntry());
      // Owner=Nickname
      prepstatement.setString(5, request.getNickname());
      prepstatement.setString(6, request.getUser());
      prepstatement.setInt(7, request.getRoomtype().getRoomtype());
      int insert = prepstatement.executeUpdate();
      if (insert >= 1) {
        // ok
        response.setCommand(Command.CREATETEMPROOM);
        response.setHeader(RESPONSE);
        response.setDataset(Protocol.DATASET);
        response.setRoom(request.getRoom());
        response.setUser(request.getUser());
        response.setEntry(request.getEntry());
        response.setLifetime(request.getLifetime());
        response.setPassword(request.getPassword());
        response.setRoomtype(request.getRoomtype());
        response.setMultilingualkey(KEY.SERVER_DER_CHATRAUM_WURDE_ANGELEGT);
      }
    }
    catch (SQLException e) {
      response.setCommand(Command.CREATETEMPROOM);
      response.setHeader(ERROR);
      response.setDataset(Protocol.DATASET);
      response.setRoom(request.getRoom());
      response.setUser(request.getUser());
      response.setRoomtype(request.getRoomtype());
      response.setErrorCode(e.getErrorCode());
      response.setMultilingualkey(KEY.SERVER_DER_CHATRAUM_IST_VORHANDEN);
    }
    if (response.getEntry() == Entry.PUBLIC) return response;
    // Alles PROTECTED was jetzt kommt
    // Besprechungsraum

    insertGroupUser(request.getUser(), request.getRoom()); // ROOM=GROUP
    return response;
  }



  /**
   * Eine Telefonkonferenz wird eingerichtet. Der Organisator wird automatisch in
   * den Raum eingetragen.
   *
   *
   * @param organisator
   *                      der Organisator ist eine userid
   * @param konferenzname
   *                      der Konferenzname
   * @param texto
   *                      eine Beschreibungstext für die Konferenz
   *
   * @return Der Rückgabewert ist {@code null}, wenn die Konferenz bereits
   *         erstellt wurde, sonst die Telko mit Attribute ohne Screenshot
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   */
  public Telko createTelefonkonferenz(String organisator, String konferenzname, String texto)
      throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_CREATE_TELEFONKONFERENZ);
    logSQLKey(Persistence.KEY_CREATE_TELEFONKONFERENZ, sql);
    Telko telko = null;

    if (isTelkoPresent(konferenzname, organisator)) return telko;

    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setString(1, konferenzname);
      prepstatement.setString(2, organisator);
      LocalDateTime aQueHora = Util.aQueHora(LocalDateTime.now());
      prepstatement.setTimestamp(3, Timestamp.valueOf(aQueHora));
      prepstatement.setString(4, texto);
      if (prepstatement.executeUpdate() > 0) {
        telko = new Telko();
        telko.setKonferenzraum(konferenzname);
        telko.setTexto(texto);
        telko.setOrganisator(organisator);
        telko.setUntil(ZonedDateTime.of(aQueHora, ZoneId.of(TIMEZONE)));
      }

      // DEr Organisator wird in den Raum eingetragen
      // konferenzraum und uid sind Pflicht
      insertTelefonkonferenzraum(konferenzname, organisator, organisator);

    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
    return telko;
  }



  private CREATETEMPROOM createTestRoom(CREATETEMPROOM request) {
    String sql = persistence.getSQL(Persistence.KEY_CREATE_ROOM);
    CREATETEMPROOM response = new CREATETEMPROOM();

    logSQLKey(Persistence.KEY_CREATE_ROOM, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql);
    ) {

      prepstatement.clearParameters();
      prepstatement.setString(1, request.getRoom());

      // PUBLIC-ROOM hat keine GROUP
      switch(request.getEntry()) {
        case PUBLIC:
          prepstatement.setString(2, null);
          break;
        case PROTECTED:
          // NEU 210205
          // Die Gruppe heisst wie der Raum.
          // Eine Gruppe muss vorhanden sein, sonst ist eine Anmeldung nicht
          // möglich
          // Die Gruppe ist nicht an einen Anwender gebunden
          prepstatement.setString(2, request.getRoom());
          break;
      }
      prepstatement.setInt(3, request.getLifetime().getLifetimeNumber());
      prepstatement.setInt(4, request.getEntry().getEntry());
      // Owner=Nickname
      prepstatement.setString(5, request.getNickname());
      prepstatement.setString(6, request.getUser());
      prepstatement.setInt(7, request.getRoomtype().getRoomtype());
      int insert = prepstatement.executeUpdate();
      if (insert >= 1) {
        // ok
        response.setCommand(Command.CREATETEMPROOM);
        response.setHeader(RESPONSE);
        response.setDataset(Protocol.DATASET);
        response.setRoom(request.getRoom());
        response.setUser(request.getUser());
        response.setEntry(request.getEntry());
        response.setLifetime(request.getLifetime());
        response.setPassword(request.getPassword());
        response.setRoomtype(request.getRoomtype());
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage());
      response.setCommand(Command.CREATETEMPROOM);
      response.setHeader(ERROR);
      response.setDataset(Protocol.DATASET);
      response.setRoom(request.getRoom());
      response.setUser(request.getUser());
      response.setErrorCode(e.getErrorCode());
      response.setMultilingualkey(KEY.SERVER_DER_CHATRAUM_IST_VORHANDEN);
    }
    return response;
  }



  /**
   * Die Methode erzeugt einen Testuser. Das Password ist für jeden User 123.
   *
   *
   * @param user
   *             ein Anwender
   *
   */
  public EntityUser createTestuser(EntityUser user) {
    String sql = persistence.getSQL(Persistence.KEY_CREATE_TESTUSER2);
    logSQLKey(Persistence.KEY_CREATE_TESTUSER2, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {

      prepStatement.setString(1, user.getUid());
      prepStatement.setString(2, user.getMail());
      prepStatement.setString(3, user.getNickname());
      prepStatement.setInt(4, user.getBenutzerstatus());
      String md5 = BCrypt.withDefaults().hashToString(12, "123".toCharArray());
      prepStatement.setString(5, md5);
      prepStatement.setBoolean(6, user.isAdmin());
      prepStatement.setBoolean(7, user.isChatdenied());
      prepStatement.setInt(8, user.getForeground());
      prepStatement.setInt(9, user.getBackground());
      prepStatement.setBoolean(10, user.isChattooltip());
      prepStatement.executeUpdate();
    }
    catch (SQLException e) {
      log.fatal(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }

    return user;
  }



  /**
   * Die Methode erzeugt Testuser mit zufälligen Pflichtattributen. Der
   * Benutzerstatus ist VOLLMITGLIED.
   *
   * @param count
   *              Anzahl der Testuser
   *
   * @return alle erzeugten Anwender
   *
   * @throws SQLException
   */
  public EntityUser[] createTestUser(int count) throws SQLException {
    EntityUser[] entityUser = new EntityUser[count];
    String sql = persistence.getSQL(Persistence.KEY_CREATE_TESTUSER);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      for (int index = 0; index < count; index++) {

        entityUser[index] = new EntityUser();

        entityUser[index].setAdmin(false);
        entityUser[index].setUid(Util.getRandomID());
        entityUser[index].setMail(Util.getRandomID());
        entityUser[index].setNickname(Util.getRandomID());
        entityUser[index].setPassword("456");
        entityUser[index].setBenutzerstatus(Benutzerstatus.VOLLMITGLIED.getStatus());
        String md5 = BCrypt.withDefaults().hashToString(12, "456".toCharArray());
        prepstatement.setString(1, entityUser[index].getUid());
        prepstatement.setString(2, entityUser[index].getMail());
        prepstatement.setString(3, entityUser[index].getNickname());
        prepstatement.setString(4, md5);
        prepstatement.setInt(5, entityUser[index].getBenutzerstatus());
        prepstatement.executeUpdate();

      }
    }
    return entityUser;
  }



  /**
   * Die Methode erzeugt Testuser mit einer UID und Nickname. Das Password ist für
   * jeden User 123.
   *
   *
   * @param users
   *              ist die UserID
   * @return {@code true}, wurden angelegt
   */
  public boolean createUser(String... users) {
    boolean done = true;
    String sql = persistence.getSQL(Persistence.KEY_CREATE_USER);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      for (String user : users) {
        logSQLKey(Persistence.KEY_CREATE_USER, sql);

        prepstatement.setString(1, user);
        prepstatement.setString(2, Util.getRandomID());
        prepstatement.setString(3, createNickname(Constants.LEN_NICKNAME)); // Nickname

        String md5 = BCrypt.withDefaults().hashToString(12, "123".toCharArray());
        prepstatement.setString(4, md5);
        prepstatement.setBoolean(5, false);
        prepstatement.executeUpdate();

      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      done = false;
    }
    return done;
  }



  /**
   * Für den Anwender wird erstmalig eine Schwarze Liste von Dateiendungen
   * erstellt. Wenn es bereits eine Liste gibt, wird sie mit neuen Endungen
   * aufgefüllt.
   *
   * @param userid
   *               die userid
   */
  public void createUserBlacklist(String userid) {
    List<String> suffixes = findSuffixes(userid);
    if (suffixes.size() == 0) return;
    StringBuilder buffer = new StringBuilder();
    buffer.append("insert into TB_USER_BLACKLIST (UID, SUFFIX, DENY) ");
    for (String tmpSuffix : suffixes) {
      buffer.append(" select '");
      buffer.append(userid);
      buffer.append("','");
      buffer.append(tmpSuffix);
      buffer.append("', 0 ");
      buffer.append("union all");
    }
    // am Ende union all entfernen
    buffer.setLength(buffer.length() - "union all".length());
    String sql = buffer.toString();
    logSQLKey(Persistence.KEY_CREATE_BLACKLIST, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      statement.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Die Tabelle TB_CHATFILE_CHUNKS wird gelöscht. Nur für Testzwecke gedacht.
   * 
   * @throws SQLException
   *                      der SQL-Befehl konnte nicht ausgeführt werden
   */
  public void deleteAllChunks() throws SQLException {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_ALL_CHUNKS);
    logSQLKey(Persistence.KEY_DELETE_ALL_CHUNKS, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
    ) {
      statement.executeUpdate(sql);
    }
  }



  /**
   * Lösche alle Dateianhänge in allen Privatechats.
   *
   */
  public void deleteAllPrivateChatfiles() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_ALL_PRIVATE_CHATFILES);
    logSQLKey(Persistence.KEY_DELETE_ALL_PRIVATE_CHATFILES, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      statement.execute(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Alle Private Chatrooms werden gelöscht. Diese Methode wird bei einem
   * Serverneustart aufgerufen.
   */
  public abstract void deleteAllPrivateChats();



  /**
   * Alle Konferenzräume werden gelöscht. Diese Methode ist nur für Testzwecke
   * gedacht.
   *
   */
  public void deleteAllTelefonkonferenzraum() {
    String sql = persistence.getSQL(Persistence.KEY_DELETEALL_KONFERENZRAUM);
    logSQLKey(Persistence.KEY_DELETEALL_KONFERENZRAUM, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }

  }



  /**
   * Lösche alle Einmal-Token in der Tabelle TB_TOKEN. Diese Methode wird beim
   * Serverneustart aufgerufen.
   */
  public void deleteAllToken() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_ALL_TOKEN);
    logSQLKey(Persistence.KEY_DELETE_ALL_TOKEN, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
    ) {
      statement.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Alle Anwender werden aus der TB_USER gelöscht. Diese Methode ist für
   * Testzwecke bestimmt.
   *
   */
  public void deleteAllUser() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_ALL_USER);
    logSQLKey(Persistence.KEY_DELETE_ALL_USER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Lösche alle Einträge in der Tabelle TB_VIDEO.
   */
  public void deleteAllVideo() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_VIDEO);
    logSQLKey(Persistence.KEY_DELETE_VIDEO, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
    ) {
      statement.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Lösche den geamten Inhalt der Tabelle TB_VIDEO_SENDER.
   */
  public void deleteAllVideoSender() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_VIDEO_SENDER);
    logSQLKey(Persistence.KEY_DELETE_VIDEO_SENDER, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
    ) {
      statement.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Eine Anlage und alle zugehörigen CHUNKS werden gelöscht.
   * 
   * @param anlagennummer
   *                      diese Anlage wird gelöscht
   * 
   * @return gelöschte Datensätze
   * 
   * @throws SQLException
   *                      der SQL-Befehl konnte nicht ausgeführt werden
   */
  public int deleteAnlage(int anlagennummer) throws SQLException {
    int deleted = 0;
    String sql = persistence.getSQL(Persistence.KEY_DELETE_ANLAGE);
    logSQLKey(Persistence.KEY_DELETE_ANLAGE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setInt(1, anlagennummer);
      deleted = statement.executeUpdate();
    }
    return deleted;
  }



  /**
   *
   * Lösche alle Anwender deren Mitgliedschaft abgelehnt wurde.
   *
   */
  public void deleteBenutzerstatus() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_BENUTZERSTATUS);
    logSQLKey(Persistence.KEY_DELETE_BENUTZERSTATUS, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
//      statement.executeQuery(sql);
      // Bugfix
      statement.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
  }



  /**
   * Alle Anhänge aus allen Besprechungsräumen löschen. Nur für Testzwecke
   * gedacht.
   * 
   * @throws SQLException
   *                      der SQL-Befehl konnte nicht ausgeführt werden.
   */
  public void deleteChatfiles() throws SQLException {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_CHATFILES);
    logSQLKey(Persistence.KEY_DELETE_CHATFILES, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
    ) {
      statement.executeUpdate(sql);
    }

  }



  /**
   * Alle Anhänge aus dem Raum chatid werden gelöscht.
   *
   * @param chatid
   *               ein nicht privater Chatraum
   */
  public void deleteChatfiles(String chatid) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_CHATID_CHATFILES);
    logSQLKey(Persistence.KEY_DELETE_CHATID_CHATFILES, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {

      statement.setString(1, chatid);
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Alle Chaträume werden gelöscht. Diese Methode ist für Testzwecke bestimmt.
   * 
   * @throws SQLException
   */
  public void deleteChatrooms() throws SQLException {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_CHATROOMS);
    logSQLKey(Persistence.KEY_DELETE_CHATROOMS, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.executeUpdate();
    }
  }



  /**
   * Setze das Confirmation-Flag.
   *
   */
  public void deleteConfirmationFlag() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_CONFIRMATION_FLAG);
    logSQLKey(Persistence.KEY_DELETE_CONFIRMATION_FLAG, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      statement.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Die Tabelle KEY_DELETE_TB_FILETRANSFER_CONFIG wird komplett gelöscht. Diese
   * Methode ist für Testzwecke bestimmt.
   *
   */
  public void deleteFiletransferConfig() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_TB_FILETRANSFER_CONFIG);
    logSQLKey(Persistence.KEY_DELETE_TB_FILETRANSFER_CONFIG, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  public boolean deleteForbiddenRoom(String room) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_FORBIDDEN_ROOM);
    logSQLKey(Persistence.KEY_DELETE_FORBIDDEN_ROOM, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement deleteROOM = connection.prepareStatement(sql)
    ) {
      deleteROOM.setString(1, room);
      int result = deleteROOM.executeUpdate();
      if (result == 0) return false; // konnte nichts löschen
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return true;
  }



  /**
   * Deletes the specified forbidden room filters.
   *
   * @param filterList
   *                   the list of forbidden room filters to be deleted
   *
   * @return {@code true} if the deletion was successful
   */
  public boolean deleteForbiddenRoomfilter(List<String> filterList) {
    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_DELETE_FORBIDDEN_ROOMFILTER);
    logSQLKey(Persistence.KEY_DELETE_FORBIDDEN_ROOMFILTER, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      for (String value : filterList) {
        prepStatement.setString(1, value);
        prepStatement.addBatch();
      }
      prepStatement.executeBatch();
      done = true;
    }
    catch (SQLException e) {
      throw new DatabaseException(e.getMessage());
    }
    return done;

  }



  /**
   * Alle Nachrichten werden zwingend aus der Tabelle TB_RECORD gelöscht. Sie ist
   * für Testzwecke gedacht.
   */
  public abstract void deleteForceRecord();



  /**
   * Alle Gruppen werden gelöscht. Diese Methode ist für Testzwecke bestimmt.
   * 
   * @throws SQLException
   */
  public void deleteGroups() throws SQLException {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_GROUPS);
    logSQLKey(Persistence.KEY_DELETE_GROUPS, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.executeUpdate();
    }
  }



  /**
   * Die Gruppe löst sich auf, weil der Chatraum nicht mehr existiert.
   *
   * @return die Anzahl der gelöschten Gruppenmitglieder
   */
  public abstract int deleteGroupUser();

  public abstract void deleteInactiveUser();



  /**
   * Alle Konferenzraumsessions werden gelöscht, wenn der Server hochfährt.
   *
   */
  public void deleteKonferenzraumSessions() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_KONFERENZRAUM_SESSIONS);
    logSQLKey(Persistence.KEY_DELETE_KONFERENZRAUM_SESSIONS, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      statement.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Alle Konferenzteilnehmer ohne eine Telko werden gelöscht.
   *
   *
   * @return die Anzahl der gelöschten Konferenzteilnehmer. Der Rückgabewert ist
   *         {@code Integer.MIN_VALUE}, wenn der Befehl nicht ausgeführt werden
   *         konnte.
   */
  public int deleteKonferenzteilnehmerOhneTelko() {
    int count = Integer.MIN_VALUE;
    String sql = persistence.getSQL(Persistence.KEY_DELETE_KONFERENZTEILNEHMER);
    logSQLKey(Persistence.KEY_DELETE_KONFERENZTEILNEHMER, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      count = statement.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return count;
  }



  /**
   * Ein Anwender wird logisch gelöscht oder wieder zurückgeholt.
   *
   * @param userid
   *               ein Anwender
   * @param delete
   *               {@code true}, wird gelöscht
   * @throws SQLException
   */
  public void deleteLogicUser(String userid, boolean delete) throws SQLException {
    String sql = persistence.getSQL(Persistence.KEY_LDELETE_USER);
    logSQLKey(Persistence.KEY_LDELETE_USER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setBoolean(1, delete);
      statement.setString(2, userid);
      statement.executeUpdate();
    }
  }



  /**
   * Lösche alle Einträge in der TB_FILETRANSFER_CONFIG, für die es keine User in
   * der TB_USER gibt. Aus der TB_USER werden keine Einträge gelöscht.
   *
   */
  public void deleteNirvanaFiletransferConfig() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_ORPHAINED_FTS_CONFIG);
    logSQLKey(Persistence.KEY_DELETE_ORPHAINED_FTS_CONFIG, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      statement.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Finde alle Suffixe, die nicht mehr auf der Sperrliste stehen, und lösche sie.
   *
   * @throws SQLException
   *
   */
  public void deleteNirvanaSuffixes() {
    String sql = persistence.getSQL(Persistence.KEY_FIND_NIRVANA_SUFFIXES);
    logSQLKey(Persistence.KEY_FIND_NIRVANA_SUFFIXES, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      statement.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Alle Anwender werden aus der TB_ONLINE gelöscht.
   *
   */
  public abstract void deleteOnline();



  /**
   * Ein Anwender wurde ausgeloggt.
   * 
   * @param userid
   *               dieser Anwender
   * @throws SQLException
   */
  public boolean deleteOnline(String userid) {
    boolean done = false;
    String sqlOnline = persistence.getSQL(Persistence.KEY_DELETE_ONLINE);
    logSQLKey(Persistence.KEY_DELETE_ONLINE, sqlOnline); // TB_ONLINE

    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatementOnline = connection.prepareStatement(sqlOnline);
    ) {
      prepstatementOnline.setString(1, userid);
      done = prepstatementOnline.executeUpdate() >= 1;
    }
    catch (SQLException e) {
      log.info(e.getMessage(), e);
    }
    return done;
  }



  /**
   * Lösche alle Anwender, die keine Serververbindung haben.
   * 
   * @throws SQLException
   * 
   */
  public List<Online> deleteOnlineKeepalive() throws SQLException {
    ArrayList<Online> onlinelist = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEEPALIVE_EXPIRED);
//    logSQLKey(Persistence.KEEPALIVE_EXPIRED, sql);

    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql)
    ) {

      while (rs.next()) {
        Online online = new Online();
        online.setSession(rs.getString(SESSION));
        online.setUid(rs.getString(UID));
        online.setKeepAlive(
            ZonedDateTime.of(rs.getTimestamp(KEEPALIVE).toLocalDateTime(), ZoneId.of(TIMEZONE))
        );
        online.setSince(ZonedDateTime.of(rs.getTimestamp(SINCE).toLocalDateTime(), ZoneId.of(TIMEZONE)));
        onlinelist.add(online);
      }
    }
    return onlinelist;
  }



  /**
   * Lösche alle Anhänge, die zu keinem Chatraum gehören.
   *
   */
  public void deleteOrphainedChatfiles() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_ORPHAINED_CHATFILES);
    logSQLKey(Persistence.KEY_DELETE_ORPHAINED_CHATFILES, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      statement.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
  }



  /**
   * Lösche alle User aus der TB_ONLINE, die nicht mehr in der TB_USER existieren.
   */
  public void deleteOrphainedOnline() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_ORPHAINED_TB_ONLINE);
    logSQLKey(Persistence.KEY_DELETE_ORPHAINED_TB_ONLINE, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      statement.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }

  }



  /**
   * Lösche alle Räume deren Owner nicht existieren. Die Räume sind in der
   * TB_CHAT.
   */
  public abstract void deleteOrphainedRoom();



  /**
   * Lösche alle Sender, die keinen Empfänger haben.
   * 
   */
  public void deleteOrphainedSender() {
    String sql = persistence.getSQL(Persistence.KEY_ORPHAINED_SENDER);
    logSQLKey(Persistence.KEY_ORPHAINED_SENDER, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      statement.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Lösche Pärchen, weil einer aufgelegt hat.
   *
   * @param uid
   *            diese Person hat aufgelegt
   */
  public void deletePartner(String uid) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_PARTNER_USERID);
    logSQLKey(Persistence.KEY_DELETE_PARTNER_USERID, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, uid);
      statement.setString(2, uid);
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Ein User ist gegangen. Seine gesamten PrivateChats müssen gelöscht werden.
   * Alle Gegenstellen müssen benachrichtigt werden.
   * 
   * @param sessionID
   *                  der Onlineschlüssel identifiziert einen Anwender
   * 
   * @return alle Onlineschlüssel der Gegenstellen
   */
  public List<String> deletePrivateChatAll(String sessionID) {
    ArrayList<String> sessionIDS = new ArrayList<>(2);
    String sqlDelete = persistence.getSQL(Persistence.KEY_DELETE_PRIVATECHATS);
    String sql = persistence.getSQL(Persistence.KEY_FETCH_REMOTESESSION);
    logSQLKey(Persistence.KEY_FETCH_REMOTESESSION, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
      PreparedStatement statementDelete = connection.prepareStatement(sqlDelete);
    ) {
      statement.setString(1, sessionID);
      try(ResultSet rs = statement.executeQuery();) {
        while (rs.next()) {
          sessionIDS.add(rs.getString("remotesession"));
        }
        logSQLKey(Persistence.KEY_DELETE_PRIVATECHATS, sqlDelete);
        statementDelete.setString(1, sessionID);
        statementDelete.setString(2, sessionID);
        statementDelete.executeUpdate();
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return sessionIDS;
  }



  /**
   * Lösche alle Dateianhänge für Anwender, die nicht online sind.
   *
   */
  public void deletePrivateChatfiles() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_PRIVATE_CHATFILES);
    logSQLKey(Persistence.KEY_DELETE_PRIVATE_CHATFILES, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      statement.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
  }



  /**
   * Der User ist gegangen. Sein PrivateChat muss gelöscht werden. Die Gegenstelle
   * wird benachrichtigt.
   *
   * @param localsession
   *                      dieser User hat die Verbindung unterbrochen
   * @param remotesession
   *                      an diesen User wird zurückgesendet.
   * @return
   */
  public void deletePrivateChatSingle(String localsession, String remotesession) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_REMOTE_LOCAL);
    logSQLKey(Persistence.KEY_DELETE_REMOTE_LOCAL, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setString(1, remotesession);
      statement.setString(2, localsession);
      statement.setString(3, localsession);
      statement.setString(4, remotesession);
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Lösche alle Datenblöcke, die zu einem PRIVATE_CHAT gehören.
   */
  public void deletePrivateChunks() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_PRIVATE_CHATFILE_CHUNKS);
    logSQLKey(Persistence.KEY_DELETE_PRIVATE_CHATFILE_CHUNKS, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      statement.execute(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Lösche alle Nachrichten aus jedem Chatraum.
   */
  public abstract void deleteRecord();



  /**
   * Lösche den Chatraum und alle zugehörigen Nachrichten. War der Raum ein
   * PROTECTED Room ist, dann lösche auch die Gruppe. Lösche alle User, die in dem
   * Chatraum sind.
   *
   * @param room
   *               Chatraum
   * @param userid
   *               UserID
   */
  public boolean deleteRoom(String room, String userid) {
    String sqlUSERCHAT = persistence.getSQL(Persistence.KEY_DELETEALL_USER_CHAT);
    String sql = persistence.getSQL(Persistence.KEY_DELETE_ROOM);
    String sqlRecord = persistence.getSQL(Persistence.KEY_DELETE_RECORD);
//    String sqlAttachment = persistence.getSQL(Persistence.KEY_DELETE_CHATID_CHATFILES);

    logSQLKey(Persistence.KEY_DELETE_ROOM, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement deleteROOM = connection.prepareStatement(sql);
      PreparedStatement deleteUSERCHAT = connection.prepareStatement(sqlUSERCHAT);
      PreparedStatement deleteRECORD = connection.prepareStatement(sqlRecord);
//        PreparedStatement deleteATTACHMENT = connection.prepareStatement(sqlAttachment);

    ) {
      deleteROOM.setString(1, room);
      deleteROOM.setString(2, userid);
      int result = deleteROOM.executeUpdate();
      if (result == 0) return false; // konnte nichts löschen
      logSQLKey(Persistence.KEY_DELETEALL_USER_CHAT, sqlUSERCHAT);

      deleteUSERCHAT.setString(1, room);
      deleteUSERCHAT.executeUpdate();

      deleteChatfiles(room);
//      logSQLKey(Persistence.KEY_DELETE_CHATID_CHATFILES, sqlAttachment);
//      deleteATTACHMENT.setString(1, room);
//      deleteATTACHMENT.executeUpdate();

      logSQLKey(Persistence.KEY_DELETE_RECORD, sqlRecord);
      deleteRECORD.setString(1, room);
      deleteRECORD.executeUpdate();

      // Die Gruppe wird aufgel�st
      deleteGroupUser();

    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return true;
  }



  public abstract void deleteScreencast();



  /**
   * Die Projektorverbindung wurde gekappt.
   *
   * @param receiverSession
   *                        der Empfänger
   */
  public void deleteScreencastUser(String receiverSession) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_SCREENCAST_USER);
    logSQLKey(Persistence.KEY_DELETE_SCREENCAST_USER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {

      prepStatement.setString(1, receiverSession);
      prepStatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
  }



  /**
   * Die Tabelle wird geleert. Nur für Testzwecke.
   */
  public void deleteTableUserOdx() {
    String sql = persistence.getSQL(Persistence.DELETE_TABLE_USER_ODX);
    logSQLKey(Persistence.DELETE_TABLE_USER_ODX, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      statement.execute(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Lösche alle Konferenzteilnehmer, die nicht in der Collection enthalten sind.
   *
   *
   * @param konferenzraum
   *                         der Konferenzname
   * @param der
   *                         Organisator
   * @param teilnehmerUserid
   *                         wer nicht in dieser Teilnehmerliste steht, wird aus
   *                         der Konferenz gelöscht
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt weren
   *
   */
  void deleteTeilnehmer(String konferenzraum, String organisator, Collection<String> teilnehmerUserid)
      throws DatabaseException {

//    select 'User1' as UID from dual union select 'User2' from dual ...

    final String union = " union ";
    StringBuilder buffer = new StringBuilder();
    for (String teilnehmer : teilnehmerUserid) {
      buffer.append("select '").append(teilnehmer).append("' as UID from dual").append(union);
    }

    String sql = persistence.getSQL(Persistence.KEY_DELETE_TELKO_TEILNEHMER);
    sql = sql.replace("XXX", Util.stripTrailing(buffer.toString(), union));
    logSQLKey(Persistence.KEY_DELETE_TELKO_TEILNEHMER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setString(1, konferenzraum);
      prepstatement.setString(2, organisator);
      prepstatement.executeUpdate();

    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }

  }



  /**
   * Eine Telefonkonferenz wird gelöscht.
   *
   * @param konferenzname
   *                      der Konferenzname
   */
  public void deleteTelefonkonferenzraum(String konferenzname) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_KONFERENZRAUM);
    logSQLKey(Persistence.KEY_DELETE_KONFERENZRAUM, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setString(1, konferenzname);
      prepstatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }

  }



  /**
   * Diese Methode ist für Testzwecke gedacht. Alle Telkos werden gelöscht.
   *
   */
  public void deleteTelko() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_TELKO);
    logSQLKey(Persistence.KEY_DELETE_TELKO, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      statement.execute(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Alle Telkos werden gelöscht, deren Ablaufdatum um mindestens 1 Minute
   * überschritten ist.
   *
   */
  public void deleteTelkos() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_TELKOS);
    logSQLKey(Persistence.KEY_DELETE_TELKOS, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      statement.execute(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Lösche einen Gruppenraum oder Pausenraum und alle zugehörigen Nachrichten.
   * Wenn der Raum eine Gruppe hatte, wird die Gruppe gelöscht.
   *
   * @param room
   */
  public void deleteTemproom(String room) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_TEMPROOM);
    String sqlDeleteRecords = persistence.getSQL(Persistence.KEY_DELETE_RECORD);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepareStatement = connection.prepareStatement(sql);
      PreparedStatement prepareStatement2 = connection.prepareStatement(sqlDeleteRecords)

    ) {
      logSQLKey(Persistence.KEY_DELETE_TEMPROOM, sql);
      prepareStatement.setString(1, room);
      prepareStatement.executeUpdate();

      logSQLKey(Persistence.KEY_DELETE_RECORD, sql);
      prepareStatement2.setString(1, room);
      prepareStatement2.executeUpdate();

      // Die Gruppe darf erst gelöscht werden, wenn zuvor der Raum gelöscht wurde.
      deleteGroupUser();

    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Lösche alle Tempräume
   */
  public abstract void deleteTemprooms();



  /**
   * Lösche ein verbrauchtes Token.
   * 
   * @param token
   *              dieses Token löschen
   */
  public boolean deleteToken(String token) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_TOKEN);
    logSQLKey(Persistence.KEY_DELETE_TOKEN, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, token);
      return statement.executeUpdate() == 1;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Lösche alle Anwender in der Tabelle TB_TOPHONE. TB_TOPHONE enthält alle
   * aktuelle geführten Telefonate.
   *
   */
  public void deleteToPhone() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_TOPHONE);
    logSQLKey(Persistence.KEY_DELETE_TOPHONE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  public abstract void deleteTransferLib();



  /**
   * Alle User x Datei Zuordnungen werden gelöscht.
   * 
   * @param userid
   *               diese UserId
   */
  public void deleteTransferLib(String userid) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_TRANSFERLIB);
    logSQLKey(Persistence.KEY_DELETE_TRANSFERLIB, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement preparedStatement = connection.prepareStatement(sql)
    ) {
      preparedStatement.setString(1, userid);
      preparedStatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }

  }



  public void deleteUploadfiles(String userid, String ip) throws SQLException {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_UPLOADFILES);
    logSQLKey(Persistence.KEY_DELETE_UPLOADFILES, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepareStatement = connection.prepareStatement(sql)
    ) {
      prepareStatement.setString(1, userid);
      prepareStatement.setString(2, ip);
      prepareStatement.executeUpdate();
    }
  }



  /**
   * Lösche genau einen User.
   *
   *
   * @param request
   *                enthält alle Protokollinformationen
   * @return die Antwort ist entweder CONFIRM oder ERROR
   */
  public DELETEUSER deleteUser(DELETEUSER request) {
    DELETEUSER deleteuser = new DELETEUSER();
    if (deleteUser(request.getUserid())) {
      deleteuser.setCommand(Command.DELETE_USER);
      deleteuser.setHeader(CONFIRM);
    }
    else {
      deleteuser.setCommand(Command.DELETE_USER);
      deleteuser.setHeader(ERROR);
    }
    return deleteuser;
  }



  /**
   *
   * Ein Anwender wird gelöscht.
   *
   * @param userid
   *               Benutzer-ID
   * @return {@code true}, der Anwender konnte gelöscht werden
   */
  public boolean deleteUser(String userid) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_USER);
    String sqlFTS = persistence.getSQL(Persistence.DELETE_FTS_CONFIG);

    if (log.isDebugEnabled()) log.debug(sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
      PreparedStatement statement2 = connection.prepareStatement(sqlFTS);

    ) {

      // Lösche immer aus TB_FILETRANSFER_CONFIG
      if (log.isDebugEnabled()) log.debug(sqlFTS);
      statement2.setString(1, userid);
      statement2.executeUpdate();

      statement.setString(1, userid);
      return statement.executeUpdate() == 0;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      return false;
    }
  }



  /**
   * Der Anwender ist offline gegangen. Falls im Privatechat noch Dateien für ihn
   * bereitstehen, so werden sie jetzt gelöscht.
   *
   *
   * @param uid
   */
  public void deleteUserChatfiles(String uid) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_USER_CHATFILES);
    logSQLKey(Persistence.KEY_DELETE_USER_CHATFILES, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, uid);
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
  }



  /**
   * Ein Anwender wird aus allen Chaträumen gelöscht.
   * 
   * @param userid
   *               diese UserId
   */
  public void deleteUserChatrooms(String userid) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_USER_CHATROOMS);
    logSQLKey(Persistence.KEY_DELETE_USER_CHATROOMS, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement preparedStatement = connection.prepareStatement(sql)
    ) {
      preparedStatement.setString(1, userid);
      preparedStatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }

  }



  /**
   * Alle Zugriffsrechte für alle Anwender für jedes Modul löschen. Nur für
   * Testzwecke.
   *
   */
  public void deleteUserModules() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_USER_MODULE);
    logSQLKey(Persistence.KEY_DELETE_USER_MODULE, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      statement.execute(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  public void deleteUserOdx(String userid) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_USER_ODX);
    logSQLKey(Persistence.KEY_DELETE_USER_ODX, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      prepStatement.setString(1, userid);
      prepStatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  public abstract void deleteUserXChat();



  /**
   * Für diesen Empfänger werden alle Einträge aus der TB_VIDEO gelöscht.
   * 
   * @param userid
   *               dieser Empfänger
   */
  public int deleteVideoReceiver(String userid) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_VIDEO_RECEIVER);
    logSQLKey(Persistence.KEY_DELETE_VIDEO_RECEIVER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql);
    ) {
      prepStatement.setString(1, userid);
      return prepStatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      return -1;
    }
  }



  /**
   * Der User wird aus der Tabelle TB_VIDEO gelöscht, wenn er nicht in der Tabelle
   * TB_SCREENCAST als Sender eingetragen ist. Er ist in einer 1:1
   * Bildschirmübertragung.
   * 
   * @param userid
   *               dieser Anwender
   */
  public void deleteVideoScreencaster(String userid) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_VIDEO_SCREENCASTER);
    logSQLKey(Persistence.KEY_DELETE_VIDEO_SCREENCASTER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      prepStatement.setString(1, userid);
      prepStatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Lösche den User XY und seinen Screenshot aus der Tabelle TB_VIDEO_SENDER.
   * 
   * @param userid
   *               dieser User ist ein Sender von Bildschirmdaten
   */
  public void deleteVideoSenderUser(String userid) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_VIDEO_SENDER_USER);
    logSQLKey(Persistence.KEY_DELETE_VIDEO_SENDER_USER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setString(1, userid);
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.fatal(e.getMessage(), e);
    }
  }



  /**
   * Für diesen Anwender werden alle Einträge für die Bildschirmübertragung aus
   * der TB_VIDEO gelöscht. Es spielt keine Rolle, ob der User Sender oder
   * Empfänger von Bildschirmdaten ist.
   * 
   * @param userid
   *               dieser User
   */
  public void deleteVideoUser(String userid) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_VIDEO_USER);
    logSQLKey(Persistence.KEY_DELETE_VIDEO_USER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      prepStatement.setString(1, userid);
      prepStatement.setString(2, userid);
      prepStatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Jemand möchte telefonieren und benötigt den PORT und die IP-Adresse der
   * Gegenseite. Die Anfrage wird positiv beantwortet, wenn die Gegenseite Anrufe
   * generell zulässt und nicht gerade telefoniert.
   *
   *
   * @param request
   *                Anfrageparameter
   * @return die Verbindungsdaten oder eine Absage
   */
  public DIAL dial(DIAL request) {
    DIAL dial = null;
    String sql = persistence.getSQL(Persistence.KEY_DIAL);
    logSQLKey(Persistence.KEY_DIAL, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, request.getReceiverNickname());
      try(ResultSet rs = statement.executeQuery()) {
        if (rs.next()) {
          final String uid = rs.getString(UID);
          // Kann die Gegenseite Anrufe entgegennehmen
          if (isOnCall(request.getReceiverNickname())) {
            // Gegenseite nimmt keine Anrufe entgegen
            dial = new DIAL();
            dial.setHeader(ERROR);
            dial.setCommand(Command.DIAL);
            dial.setDataset(Protocol.DATASET);
            dial.setReceiverNickname(request.getReceiverNickname());
            dial.setMultilingualkey(KEY.STRING_NIMMT_KEINE_ANRUFE_ENTGEGEN);
            return dial;
          }
          // telefoniert die Gegenseite?
          if (isOnBusy(request.getReceiverNickname())) {
            // Gegenseite telefoniert
            dial = new DIAL();
            dial.setHeader(ERROR);
            dial.setCommand(Command.DIAL);
            dial.setDataset(Protocol.DATASET);
            dial.setReceiverNickname(request.getReceiverNickname());
            dial.setMultilingualkey(KEY.STRING_IST_AM_TELEFONIEREN);
            return dial;
          }

          // ist die Gegenseite in einer Konferenz
          boolean isBlocked = blockConferenceCall(uid);
          if (isBlocked) {
            dial = new DIAL();
            dial.setHeader(ERROR);
            dial.setCommand(Command.DIAL);
            dial.setDataset(Protocol.DATASET);
            dial.setReceiverNickname(request.getReceiverNickname());
            dial.setMultilingualkey(KEY.STRING_IST_IN_EINER_KONFERENZ);
            return dial;
          }

          dial = new DIAL();
          dial.setHeader(RESPONSE);
          dial.setCommand(Command.DIAL);
          dial.setDataset(Protocol.DATASET);
          dial.setCallerVoice(request.getCallerVoice());
          dial.setCallerNickname(request.getCallerNickname());
          dial.setCallerUserid(request.getCallerUserid());
          dial.setReceiverNickname(request.getReceiverNickname());
          dial.setReceiverUserid(uid);
        }
        else {
          // error nicht gefunden
          // HEADER ERROR
          dial = new DIAL();
          dial.setHeader(ERROR);
          dial.setCommand(Command.DIAL);
          dial.setDataset(Protocol.DATASET);
          dial.setReceiverNickname(request.getReceiverNickname());
          dial.setMultilingualkey(KEY.STRING_IST_NICHT_ERREICHBAR);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.fillInStackTrace());
      dial = new DIAL();
      dial.setHeader(ERROR);
      dial.setCommand(Command.DIAL);
      dial.setDataset(Protocol.DATASET);
      dial.setReceiverNickname(request.getReceiverNickname());
      dial.setErrorMessage("Ein generischer Fehler ist in der Datenbank aufgetreten.");
    }
    return dial;
  }



  /**
   * Der Anwender möchte einen Raum betreten.
   *
   *
   * @param request
   *                dieser Request
   * @return diese Response
   */
  public ENTERROOM enterRoom(ENTERROOM request) {

    // Hat der User eine Berechtigung für den Chatraum?
    // Ist der Raum public, sofort reinlassen

    ENTERROOM response = new ENTERROOM();
    response.setCommand(Command.ENTERROOM);
    response.setDataset(Protocol.DATASET);

    Roomtype isRoomtypePublic = isPublicRoom(request.getRoom());

    if (isRoomtypePublic != null) {
      insertRoom(request.getUserid(), request.getRoom());
      response.setHeader(RESPONSE);
      response.setRoom(request.getRoom());
      response.setUserid(request.getUserid());
      response.setNickname(request.getNickname());
      response.setRoomtype(isRoomtypePublic);
      return response;
    }
    else {
      Roomtype isRoomtypeProtected = isProtectedRoom(request.getUserid(), request.getRoom());
      if (isRoomtypeProtected != null) {
        insertRoom(request.getUserid(), request.getRoom());
        response.setHeader(RESPONSE);
        response.setRoom(request.getRoom());
        response.setUserid(request.getUserid());
        response.setNickname(request.getNickname());
        response.setRoomtype(isRoomtypeProtected);
        return response;
      }
      else {
        String isocode = fetchLanguage(request.getUserid());
        MultilingualString zutritt = new MultilingualString(
            KEY.SERVER_ZUTRITTSBERECHTIGUNG, ISO639.fromValue(isocode)
        );
        response.setHeader(ERROR);
        response.setMultilingualkey(KEY.SERVER_ZUTRITTSBERECHTIGUNG);
        response.setText(zutritt.toString());
      }
    }
    return response;
  }



  public ChatUser fetch(String userid) {
    ChatUser chatuser = new ChatUser();
    EntityUser user = fetchUser(userid);
    chatuser.setBackgroundColor(user.getBackground());
    chatuser.setForegroundColor(user.getForeground());
    chatuser.setNickname(user.getNickname());
    chatuser.setUserid(userid);
    return chatuser;
  }



  /**
   * Hole einen AES Schlüssel.
   * 
   * @param sessionid
   *                  diese {@code sessionid} gehört einem Anwender, der online
   *                  ist
   * 
   * @return dieser AES Schlüssel
   */
  public String fetchAES(String sessionid) {
    String sql = persistence.getSQL(Persistence.KEY_FETCH_AES);
    logSQLKey(Persistence.KEY_FETCH_AES, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setString(1, sessionid);
      try(ResultSet rs = statement.executeQuery()) {
        rs.next();
        return rs.getString(AES);
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Lies die Dateigröße der Anlage aus.
   * 
   * @param anlage
   *               diese Anlage
   * 
   * @return die Größe in Bytes
   * 
   * @throws DatabaseException
   *                           das Lesen der Dateigröße löste einen
   *                           Verarbeitungsfehler aus
   */
  public InputStream fetchAnlageAsStream(Long anlage, Integer chunk) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_FETCH_ANLAGE);
    logSQLKey(Persistence.KEY_FETCH_ANLAGE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {

      statement.setLong(1, anlage);
      statement.setInt(2, chunk);
      try(ResultSet rs = statement.executeQuery();) {
        if (rs.next()) {
          return rs.getBlob("FILE").getBinaryStream();
        }
        else {
          throw new DatabaseException(
              "(" + anlage + "/" + chunk + ") - dieser Datenblock ist nicht vorhanden"
          );
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   *
   * Ermittle alle private Pausenräume, die der übergebene USER betreten könnte.
   *
   * @return alle Pausenräume
   */
  public List<String> fetchBreakrooms(String userid) {
    ArrayList<String> rooms = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_READ_BREAKROOMS);
    logSQLKey(Persistence.KEY_READ_BREAKROOMS, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, userid);
      try(ResultSet rs = statement.executeQuery()) {
        while (rs.next()) {
          String chatid = rs.getString(1);
          rooms.add(chatid);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return rooms;
  }



  /**
   * Finde alle Anwender, die der Telefonkonferenz beitreten könnten. Der
   * Organisator darf der Liste nicht angehören.
   *
   * @param konferenzraum
   *                      der Name für den Konferenzraum
   * @param Organisator
   *                      der Organisator
   *
   * @return die Map enthält n-Paare (Nickname/Userid)
   */
  public TreeMap<String, String> fetchCandidateConferenceCall(String konferenzraum, String organisator) {
    TreeMap<String, String> map = new TreeMap<>();
    String sql = persistence.getSQL(Persistence.KEY_CANDIDATE_KONFERENZRAUM);
    logSQLKey(Persistence.KEY_CANDIDATE_KONFERENZRAUM, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {

      statement.setString(1, konferenzraum);
      statement.setString(2, organisator);
      try(ResultSet rs = statement.executeQuery()) {
        while (rs.next()) {
          map.put(rs.getString(NICKNAME), rs.getString("TB_USER.UID"));
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return map;
  }



  public CANDIDATETOPICMEMBER fetchCandidatemember(CANDIDATETOPICMEMBER request) {
    CANDIDATETOPICMEMBER response = new CANDIDATETOPICMEMBER();
    String sql = persistence.getSQL(Persistence.KEY_CANDIDATE_MEMBER);
    logSQLKey(Persistence.KEY_CANDIDATE_MEMBER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {
      prepstatement.setString(1, request.getRoom());
      try(ResultSet rs = prepstatement.executeQuery();) {
        ArrayList<ChatUser> memberarray = new ArrayList<>();
        while (rs.next()) {
          ChatUser room = new ChatUser();
          room.setNickname(rs.getString("NICKNAME"));
          room.setUserid(rs.getString("UID"));
          memberarray.add(room);
        }
        response.setHeader(RESPONSE);
        response.setCommand(Command.CANDIDATETOPICMEMBER);
        response.setDataset(Protocol.DATASET);
        response.setUserid(request.getUserid());
        response.setRoom(request.getRoom());
        response.setRoomtype(Roomtype.toRoomtype(fetchRoomtype(request.getRoom())));
        response.setChatUser(memberarray.toArray(new ChatUser[memberarray.size()]));
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return response;
  }



  /**
   * Lies die Dateigröße der Anlage aus.
   * 
   * @param anlage
   *               diese Anlage
   * 
   * @return die Größe in Bytes
   * 
   * @throws DatabaseException
   *                           das Lesen der Dateigröße löste einen
   *                           Verarbeitungsfehler aus
   */
  public long fetchChatAnlageSize(long anlage) throws DatabaseException {
    long size = 0;
    String sql = persistence.getSQL(Persistence.KEY_FETCH_ANLAGE_SIZE);
    logSQLKey(Persistence.KEY_FETCH_ANLAGE_SIZE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {

      statement.setLong(1, anlage);
      try(ResultSet rs = statement.executeQuery();) {
        if (rs.next()) {
          size = rs.getLong(FILESIZE);
        }
      }
    }
    catch (SQLException e) {
      log.info(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
    return size;

  }



  /**
   * Sende eine Liste aller Anwender mit ihren Attributen aus einem Chatraum.
   *
   * @param chatroom
   *                 ein Chatraum
   *
   * @return die Attribute eines Anwenders wie UID, NICKNAME, Vordergrundfarbe,
   *         Hintergrundfarbe etc.
   */
  public CHATUSERLIST fetchChatuserlist(String chatroom) {
    CHATUSERLIST response = new CHATUSERLIST();
    response.setCommand(Command.CHATUSERLIST);
    response.setHeader(ERROR);
    String sql = persistence.getSQL(Persistence.KEY_FETCH_CHATUSERLIST);
    logSQLKey(Persistence.KEY_FETCH_CHATUSERLIST, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setString(1, chatroom);
      try(ResultSet rs = prepstatement.executeQuery()) {
        ArrayList<ChatUser> roomarray = new ArrayList<>();
        while (rs.next()) {
          ChatUser chatuser = new ChatUser();
          chatuser.setBackgroundColor(rs.getInt(BACKGROUND));
          chatuser.setForegroundColor(rs.getInt(FOREGROUND));
          chatuser.setNickname(rs.getString("NICKNAME"));
          chatuser.setUserid(rs.getString("UID"));
          roomarray.add(chatuser);
        }
        response.setCommand(Command.CHATUSERLIST);
        response.setHeader(RESPONSE);
        response.setDataset(Protocol.DATASET);
        response.setRoom(chatroom);
        response.setChatUser(roomarray.toArray(new ChatUser[roomarray.size()]));
        return response;
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage());
      response.setDataset(Protocol.DATASET);
      response.setErrorCode(e.getErrorCode());
      response.setErrorMessage(e.getMessage());
    }
    return response;
  }



  /**
   * Hole für eine Anlage einen Chunk.
   * 
   * @param anlage
   *               diese Anlagennummer
   * @param chunk
   *               diese Chunknummer
   * @throws SQLException
   * @throws IOException
   */
  public ChatfileChunk fetchChunk(Long anlage, int chunk) throws SQLException, IOException {
    ChatfileChunk entity = null;
    String sql = persistence.getSQL(Persistence.KEY_FETCH_CHUNK);
    logSQLKey(Persistence.KEY_FETCH_CHUNK, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql);
    ) {
      prepstatement.setLong(1, anlage);
      prepstatement.setInt(2, chunk);
      try(ResultSet rs = prepstatement.executeQuery();) {

        if (rs.next()) {
          entity = new ChatfileChunk();
          entity.setAnlagenummer(anlage);
          entity.setChunknummer(chunk);
          Blob blob = rs.getBlob("FILE");
          byte buffer[] = new byte[8192];
          try(
            InputStream is = blob.getBinaryStream();
            ByteArrayOutputStream out = new ByteArrayOutputStream()
          ) {
            int read;
            while ((read = is.read(buffer)) != -1) {
              out.write(buffer, 0, read);
            }
            entity.setContent(out.toByteArray());
          }
        }
      }
    }
    return entity;
  }



  /**
   * Gib alle Chunknummern einer Anlage zurück. Die kleinste Nummer ist der erste
   * Datenblock.
   * 
   * @param anlage
   *               diese Anlage/Attachment
   * 
   * @return sortierte Chunknummern
   * @throws DatabaseException
   *                           die Chunkliste konnte nicht erstellt werden
   */
  public ArrayList<Long> fetchChunknumbers(Long anlage) throws DatabaseException {
    ArrayList<Long> list = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_FETCH_CHUNKNUMBERS);
    logSQLKey(Persistence.KEY_FETCH_CHUNKNUMBERS, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql);
    ) {
      prepstatement.setLong(1, anlage);
      try(ResultSet rs = prepstatement.executeQuery();) {
        while (rs.next()) {
          list.add(rs.getLong("CHUNK"));
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException("die Chunkliste konnte nicht erstellt werden");
    }
    return list;
  }



  /**
   * Die Konfigurationsdaten für die Domäne werden ausgelesen. Der Rückgabewert
   * ist nie {@code null}. Einzelne Attribute von {@code Config} können null sein.
   *
   *
   * @return die Konfigurationsdaten
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   */
  public Config fetchConfig() throws DatabaseException {
    Config config = new Config();
    String sql = persistence.getSQL(Persistence.KEY_READ_CONFIG);
    logSQLKey(Persistence.KEY_READ_CONFIG, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql)
    ) {
      if (rs.next()) {
        config.setDomain(rs.getString("DOMAIN"));
        config.setIsPublic(rs.getBoolean("ISPUBLIC"));
        config.setFlipToForum(rs.getInt("FLIPTOFORUM"));
        config.setHours(rs.getInt("HOURS"));
        config.setAccount(rs.getInt("ACCOUNT"));
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
    return config;
  }



  /**
   * Alle bestätigten Anwender werden in einer Liste erfasst. Der Benutzerstatus
   * ist 3=VOLLMITGLIEDSCHAFT_BESTÄTIGT
   *
   * @return alle Anwender mit dem Status VOLLMITGLIEDSCHAFT_BESTÄTIGT
   */
  public List<EntityUser> fetchConfirmedUser() {
    ArrayList<EntityUser> userliste = new ArrayList<>();

    String sql = persistence.getSQL(Persistence.KEY_READ_CONFIRM_MEMBERSHIP);
    logSQLKey(Persistence.KEY_READ_CONFIRM_MEMBERSHIP, sql);

    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql)
    ) {

      while (rs.next()) {
        EntityUser user = new EntityUser();
        user.setUid(rs.getString("UID"));
        user.setMail(rs.getString("MAIL"));
        user.setNickname(rs.getString("NICKNAME"));
        user.setPassword(rs.getString("PASSWORD"));
        Timestamp timestamp = rs.getTimestamp(TIME);
        user.setTime(ZonedDateTime.of(timestamp.toLocalDateTime(), ZoneId.of(TIMEZONE)));
        user.setBenutzerstatus(rs.getInt("BENUTZERSTATUS"));
        userliste.add(user);
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return userliste;
  }



  /**
   * Lies die Benutzerdaten für den FileTransferService aus.
   *
   *
   * @param userid
   *               von diesem Benutzer werden die Daten gelesen
   * @return die Benutzerdaten
   */
  public FiletransferConfig fetchFiletransferConfig(String userid) {
    FiletransferConfig config = new FiletransferConfig();
    String sql = persistence.getSQL(Persistence.KEY_READ_FTS_CONFIG);

    logSQLKey(Persistence.KEY_READ_FTS_CONFIG, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, userid);

      try(ResultSet rs = statement.executeQuery()) {
        if (rs.next()) {
          config.setPort(rs.getInt("PORT"));
          config.setStarttype(rs.getInt("STARTTYPE"));
          config.setUid(rs.getString("UID"));
          config.setDownloadDir(rs.getString("DOWNLOADDIR"));
          config.setUploadDir(rs.getString("UPLOADDIR"));
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return config;
  }



  /**
   *
   * Lies alle Dateitypen aus. Die zurückgegebene List ist nie {@code null}.
   *
   *
   * @return alle Dateitypen, die gesperrt werden könnten
   */
  public List<BlacklistTypes> fetchFiletypes(String userid) {
    ArrayList<BlacklistTypes> blacklist = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_FETCH_SUFFIXES);
    logSQLKey(Persistence.KEY_FETCH_SUFFIXES, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {

      statement.setString(1, userid);
      try(ResultSet rs = statement.executeQuery()) {
        while (rs.next()) {
          BlacklistTypes item = new BlacklistTypes();
          item.setDescription(rs.getString("DESCRIPTION"));
          item.setSuffix(rs.getString("TB_USER_BLACKLIST.SUFFIX"));
          item.setChecked(rs.getBoolean("DENY"));
          blacklist.add(item);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return blacklist;
  }



  /**
   * Eine Verbotsliste wird erstellt.
   *
   * @return verbotene Namen
   */
  public List<String> fetchForbiddenNames() {
    ArrayList<String> forbidden = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.FETCH_FORBIDDEN_ROOM);
    logSQLKey(Persistence.FETCH_FORBIDDEN_ROOM, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql)
    ) {

      while (rs.next()) {
        forbidden.add(rs.getString("FORBIDDEN"));
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return forbidden;
  }



  /**
   * Alle verbotenen Nicknames werden geholt.
   *
   * @return die verbotenen Namen
   */
  public List<String> fetchForbiddenNicknames() {
    ArrayList<String> nicknames = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_FETCH_NICKNAMES);
    logSQLKey(Persistence.KEY_FETCH_NICKNAMES, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql)
    ) {

      while (rs.next()) {
        nicknames.add(rs.getString("NICKNAME"));
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return nicknames;
  }



  /**
   * Diese Räume müssen aus der Tabelle TB_CHAT gelöscht werden, weil sie verboten
   * sind.
   *
   * @return verbotene Räume
   */
  public List<String> fetchForbiddenRooms() {
    ArrayList<String> forbidden = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_FORBIDDEN_ROOMS);
    logSQLKey(Persistence.KEY_FORBIDDEN_ROOMS, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      try(ResultSet rs = statement.executeQuery(sql)) {
        while (rs.next()) {
          forbidden.add(rs.getString(CHATID));
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return forbidden;
  }



  /**
   * Ein Anwender betritt einen Raum und fordert eine HISTORYMESSAGE an.
   *
   *
   * @param room
   *                       der Chatraum
   * @param textBufferSize
   *                       die Größe wird in der web.xml festgelegt
   * @return die Antwort auf eine HISTORYMESSAGE-Anfrage
   */
  public HISTORYMESSAGE fetchHistorymessage(String room) {
    Stack<Attachment> alleNachrichtenStack = historymessage(room);
    Stack<Attachment> result = new Stack<>();
    int index = 0;
    // In einem Chunk dürfen nicht mehr als 64 Nachrichten übertragen werden.
    // Constants.HISTORY_MESSAGES muss <=64 sein
    while (!alleNachrichtenStack.isEmpty() && index < Constants.HISTORY_MESSAGES) {
      result.push(alleNachrichtenStack.pop());
      index++;
    }
    Collections.reverse(result);
    alleNachrichtenStack.clear();

    HISTORYMESSAGE response = new HISTORYMESSAGE();
    response.setCommand(Command.HISTORYMESSAGE);
    response.setHeader(RESPONSE);
    response.setDataset(Protocol.DATASET);
    response.setRoom(room);

    response.setAttachment(result.toArray(new AttachmentImpl[result.size()]));
    return response;
  }



  /**
   * Nach wie vielen Stunden muss eine Nachricht im Chatmodul gelöscht werden?
   * 
   * @return Stunden
   */
  public int fetchHours() {
    String sql = persistence.getSQL(Persistence.KEY_SELECT_HOURS);
    logSQLKey(Persistence.KEY_SELECT_HOURS, sql);
    int hours = Constants.MAX_DURATION_TIME;
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql)
    ) {
      if (rs.next()) {
        hours = rs.getInt("HOURS");
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return hours;
  }



  /**
   * Lies alle TV Sender aus. Die Liste ist immer definiert.
   *
   *
   * @return eine Liste mit TV Sendern
   */
  public List<Iptv> fetchIptv() {
    ArrayList<Iptv> liste = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_FETCH_IPTV);
    logSQLKey(Persistence.KEY_FETCH_IPTV, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql)
    ) {

      while (rs.next()) {
        Iptv iptv = new Iptv();
        iptv.setIpsender(rs.getString("IPSENDER"));
        iptv.setUrl(rs.getString("URL"));
        liste.add(iptv);
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return liste;
  }



  /**
   * Gibt es einen aktiven Konferenzteilnehmer, der seinen Bildschirm teilt?
   * 
   * @param konferenzraum
   *                      dieser Konfernzraum
   * @param organisator
   *                      dieser Oranisator ist eine Userid und hat die Telko
   *                      eingerichtet
   * 
   * @return dieser Teilnehmer teilt seinen Bildschirm oder {@code null} für kein
   *         Teilnehmer
   * 
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   */
  public String fetchKonferenzraumActiveUser(String konferenzraum, String organisator)
      throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_FETCH_KONFERENZRAUM_ACTIVE_USER);
    logSQLKey(Persistence.KEY_FETCH_KONFERENZRAUM_ACTIVE_USER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {

      statement.setString(1, konferenzraum);
      statement.setString(2, organisator);
      try(ResultSet rs = statement.executeQuery()) {
        if (rs.next()) {
          return rs.getString(SENDER);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }
    return null;
  }



  /**
   * Finde für einen Konferenzraum alle SessionIds. Über die SessionIds kann
   * später ein Broadcast gesendet werden.
   *
   *
   * @param konferenzraum
   *                      der Konferenzraum
   * @param organisator
   *                      der Organisator
   * @return alle Sessions
   */
  public List<String> fetchKonferenzraumSessions(String konferenzraum, String organisator) {
    ArrayList<String> sessionIds = new ArrayList<>();

    String sql = persistence.getSQL(Persistence.KEY_FETCH_KONFERENZRAUM_SESSIONS);
    logSQLKey(Persistence.KEY_FETCH_KONFERENZRAUM_SESSIONS, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {

      statement.setString(1, konferenzraum);
      statement.setString(2, organisator);
      try(ResultSet rs = statement.executeQuery();) {
        while (rs.next()) {
          sessionIds.add(rs.getString(SESSION));
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }

    return sessionIds;
  }



  /**
   * Hole vom Anwender die Spracheinstellung.
   * 
   * @param userid
   *               dieser Anwender
   * @return die Spracheinstellung de, en, es
   * 
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht verarbeitet werden
   */
  public String fetchLanguage(String userid) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_FETCH_LANGUAGE);
    logSQLKey(Persistence.KEY_FETCH_LANGUAGE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepareStatement = connection.prepareStatement(sql)
    ) {

      prepareStatement.setString(1, userid);
      try(ResultSet rs = prepareStatement.executeQuery()) {
        if (rs.next()) {
          return rs.getString(1);
        }
        throw new DatabaseException("generischer Fehler");
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }

  }



  /**
   * Lies alle Module aus, für die der Anwender eine Zugriffsberechtigung hat.
   *
   *
   * @param userid
   *               der Anwender
   *
   * @return alle Module mit Zugriffsberechtigung
   *
   */
  public List<UserModule> fetchModules(String userid) {
    ArrayList<UserModule> list = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_FETCH_MODULE);
    logSQLKey(Persistence.KEY_FETCH_MODULE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      prepStatement.setString(1, userid);
      try(ResultSet rs = prepStatement.executeQuery()) {
        while (rs.next()) {
          UserModule module = new UserModule();
          module.setModule(rs.getInt(MODULE));
          module.setUserid(rs.getString(UID));
          list.add(module);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return list;
  }



  /**
   * Hole den Nickname von einem User.
   * 
   * @param userid
   *               dieser User
   * @return dieser Nickname oder {@code null}, wenn keiner gefunden wurde
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   */
  public String fetchNickname(String userid) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_FETCH_NICKNAME);
    logSQLKey(Persistence.KEY_FETCH_NICKNAME, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepareStatement = connection.prepareStatement(sql)
    ) {
      prepareStatement.setString(1, userid);
      try(ResultSet rs = prepareStatement.executeQuery()) {
        return rs.next() ? rs.getString(1) : null;
      }
    }
    catch (SQLException e) {
      log.fatal(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }

  }



  /**
   * Hole das Verzeichnis, aus dem der ODX-Anwender zuletzt Dateien las.
   *
   * @param userid
   *               der ODX-Anwender
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   */
  public UserOdx fetchOdxLoadDirectory(String userid) throws DatabaseException {
    UserOdx result = new UserOdx();
    String sql = persistence.getSQL(Persistence.KEY_FETCH_ODX_LASTDIR);
    logSQLKey(Persistence.KEY_FETCH_ODX_LASTDIR, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      prepStatement.setString(1, userid);

      try(ResultSet rs = prepStatement.executeQuery()) {
        if (rs.next()) {
          result.setLastdir(rs.getString("LASTDIR"));
          result.setUid(userid);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
    return result;
  }



  /**
   * Lies für alle User die ODX Berechtigung aus.
   *
   * @return
   *
   */
  public ArrayList<TransferOdxModulPermission> fetchOdxuser() {
    ArrayList<TransferOdxModulPermission> list = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_FETCH_ODXUSER);
    logSQLKey(Persistence.KEY_FETCH_ODXUSER, sql);
    try(
      Connection connection = pool.getConnection();
      Statement prepStatement = connection.createStatement();
      ResultSet rs = prepStatement.executeQuery(sql)
    ) {

      while (rs.next()) {
        TransferOdxModulPermission transfer = new TransferOdxModulPermission();
        transfer.setOdxAllow(false);
        transfer.setUserid(rs.getString(UID));
        transfer.setOdxAllow(rs.getObject(MODULE) == null ? false : true);
        transfer.setMail(rs.getString(MAIL));
        transfer.setNickname(rs.getString(NICKNAME));
        list.add(transfer);
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return list;
  }



  /**
   * Lies alle Daten eines Anwenders aus der TB_ONLINE aus.
   * 
   * @param userid
   *               dieser User wird gelesen
   * 
   * 
   * @return diese Attribute oder {@code null}, wenn der Anwender nicht mehr
   *         online ist
   */
  public Online fetchOnlineByUid(String userid) {
    String sql = persistence.getSQL(Persistence.KEY_FETCH_ONLINE_BY_UID);
    logSQLKey(Persistence.KEY_FETCH_ONLINE_BY_UID, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {

      statement.setString(1, userid);
      try(ResultSet rs = statement.executeQuery()) {
        if (!rs.next()) {
          log.info(userid + "ist nicht online");
          return null;
        }
        Online online = new Online();
        online.setAes(rs.getString(AES));
        online.setOnBusy(rs.getBoolean(ONBUSY));
        online.setAgent(Agent.toAgent(rs.getInt(AGENT)));
        online.setKeepAlive(
            ZonedDateTime.of(rs.getTimestamp(KEEPALIVE).toLocalDateTime(), ZoneId.of(TIMEZONE))
        );
        online.setSession(rs.getString(SESSION));
        online.setSince(ZonedDateTime.of(rs.getTimestamp(SINCE).toLocalDateTime(), ZoneId.of(TIMEZONE)));
        online.setUid(rs.getString(UID));
        return online;
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Ermittle die Anzahl der User in jedem Raum.
   *
   * @return
   * @throws DatabaseException
   */
  public CHATONLINELIST fetchOnlinelist() throws DatabaseException {
    CHATONLINELIST response = new CHATONLINELIST();

    String sql = persistence.getSQL(Persistence.KEY_FETCH_ONLINELIST);
    logSQLKey(Persistence.KEY_FETCH_ONLINELIST, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql);
    ) {

      ArrayList<ChatOnline> roomarray = new ArrayList<>();
      while (rs.next()) {
        ChatOnline room = new ChatOnline();
        room.setRoom(rs.getString(CHATID));
        room.setOnline(rs.getInt("ONLINE"));
        roomarray.add(room);
      }
      response.setChatOnline(roomarray.toArray(new ChatOnline[roomarray.size()]));
      response.setHeader(REQUEST);
      response.setCommand(Command.CHATONLINELIST);
      response.setDataset(Protocol.DATASET);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      response.setCommand(Command.CHATONLINELIST);
      response.setHeader(ERROR);
      throw new DatabaseException(e.getMessage());
    }
    return response;
  }



  /**
   * Lies einen Konferenzteilnehmer aus, der online ist.
   * 
   * @param userid
   *               dieser Anwender
   * @return dieser Konferenzteilnehmer oder {@code null}, wenn nicht vorhanden
   */
  public Konferenzraum fetchOnlineParticipant(String userid) {
    String sql = persistence.getSQL(Persistence.KEY_FETCH_KONFERENZTEILNEHMER);
    logSQLKey(Persistence.KEY_FETCH_KONFERENZTEILNEHMER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepareStatement = connection.prepareStatement(sql)
    ) {
      prepareStatement.setString(1, userid);
      try(ResultSet rs = prepareStatement.executeQuery()) {
        if (rs.next()) {
          Konferenzraum konferenzraum = new Konferenzraum();
          konferenzraum.setKonferenzraum(rs.getString(KONFERENZRAUM));
          konferenzraum.setOrganisator(rs.getString(ORGANISATOR));
          return konferenzraum;
        }
        else {
          return null;
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Eine Liste aller Räume mit mindestens einem Anwesenden.
   *
   * @return eine Raumliste
   */
  public List<String> fetchOnlineRoomlist() {
    ArrayList<String> rommlist = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_ONLINE_ROOMLIST);
    logSQLKey(Persistence.KEY_ONLINE_ROOMLIST, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql)
    ) {
      while (rs.next()) {
        rommlist.add(rs.getString(1));
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return rommlist;
  }



  /**
   * Alle veralteten Programmversionen werden geholt.
   *
   * @return
   */
  public List<String> fetchOutdated() {
    ArrayList<String> outdated = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_FETCH_OUTDATED);
    logSQLKey(Persistence.KEY_FETCH_OUTDATED, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql)
    ) {

      while (rs.next()) {
        outdated.add(rs.getString(1));
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return outdated;
  }



  /**
   * Hole den Hashwert für das Passwort aus der TB_USER.
   * 
   * @param userid
   *               diese userid
   * @param mail
   *               diese mailadresse
   * 
   * @return dieses Passwort als Hashwert oder {@code null}, wenn nicht vorhanden
   */
  String fetchPassword(String userid, String mail) {
    String sql = persistence.getSQL(Persistence.KEY_FETCH_PASSWORD);
    logSQLKey(Persistence.KEY_FETCH_PASSWORD, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {

      statement.setString(1, userid);
      statement.setString(2, mail);
      try(ResultSet rs = statement.executeQuery()) {
        if (rs.next()) {
          return rs.getString("PASSWORD");
        }
        if (userid != null) throw new DatabaseException(userid + " - Passwort wurde nicht gefunden.");
        if (mail != null) throw new DatabaseException(mail + " - Passwort wurde nicht gefunden.");
        throw new DatabaseException("mail und userid sind null.");
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Lies die Playerlautstärke aus.
   *
   * @param userid
   *               eine Benutzer-ID
   *
   * @return die Lautstärke
   *
   */
  public int fetchPlayervolume(String userid) {
    String sql = persistence.getSQL(Persistence.KEY_FETCH_PLAYERVOLUME);
    logSQLKey(Persistence.KEY_FETCH_PLAYERVOLUME, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {

      statement.setString(1, userid);
      try(ResultSet rs = statement.executeQuery()) {
        if (rs.next()) {
          return rs.getInt(1);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return 68;
  }



  /**
   * Lies die Dateigröße der Anlage aus. Die Datei liegt in einem Private Chat.
   * 
   * @param anlage
   *               diese Anlage
   * 
   * @return die Größe in Bytes
   * 
   * @throws DatabaseException
   *                           das Lesen der Dateigröße löste einen
   *                           Verarbeitungsfehler aus
   */
  public long fetchPrivateAnlageSize(long anlage) throws DatabaseException {
    long size = 0;
    String sql = persistence.getSQL(Persistence.KEY_FETCH_PRIVATE_ANLAGE_SIZE);
    logSQLKey(Persistence.KEY_FETCH_PRIVATE_ANLAGE_SIZE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {

      statement.setLong(1, anlage);
      try(ResultSet rs = statement.executeQuery();) {
        if (rs.next()) {
          size = rs.getLong(FILESIZE);
        }
      }
    }
    catch (SQLException e) {
      log.info(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
    return size;

  }



  public InputStream fetchPrivateChunk(Long anlage, Integer chunk) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_FETCH_PRIVATE_CHUNK);
    logSQLKey(Persistence.KEY_FETCH_PRIVATE_CHUNK, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql);
    ) {
      prepstatement.setLong(1, anlage);
      prepstatement.setInt(2, chunk);
      try(ResultSet rs = prepstatement.executeQuery()) {
        if (rs.next()) {
          return rs.getBlob("FILE").getBinaryStream();
        }
        else {
          throw new DatabaseException(
              "(" + anlage + "/" + chunk + ") - dieser Datenblock ist nicht vorhanden"
          );
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(
          "(" + anlage + "/" + chunk + ") - dieser Datenblock konnte nicht gelesen werden"
      );
    }
  }



  /**
   * Gib alle Chunknummern einer Anlage zurück. Die kleinste Nummer ist der erste
   * Datenblock.
   * 
   * @param anlage
   *               diese Anlage/Attachment
   * 
   * @return sortierte Chunknummern
   * @throws DatabaseException
   *                           die Chunkliste konnte nicht erstellt werden
   */
  public ArrayList<Long> fetchPrivateChunknumbers(Long anlage) throws DatabaseException {
    ArrayList<Long> list = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_FETCH_PRIVATE_CHUNKNUMBERS);
    logSQLKey(Persistence.KEY_FETCH_PRIVATE_CHUNKNUMBERS, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql);
    ) {
      prepstatement.setLong(1, anlage);
      try(ResultSet rs = prepstatement.executeQuery()) {
        while (rs.next()) {
          list.add(rs.getLong("CHUNK"));
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException("die Chunkliste konnte nicht erstellt werden");
    }
    return list;
  }



  /**
   * Hole von allen Videoempfängern die Websocketsession.
   * 
   * @param sender
   *               dieser Sender ist eine userid
   * @return an diese Sessions kann gesendet werden
   */
  public List<String> fetchReceiversBySender(String sender) {
    ArrayList<String> session = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_FETCH_RECEIVERS_BY_SENDER);
    logSQLKey(Persistence.KEY_FETCH_RECEIVERS_BY_SENDER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setString(1, sender);
      try(ResultSet rs = statement.executeQuery()) {
        while (rs.next()) {
          session.add(rs.getString(SESSION));
        }
        return session;
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Alle Chaträume werden angezeigt.
   *
   * @return
   * @throws DatabaseException
   */
  public ROOMLIST fetchRoomlist() throws DatabaseException {
    ROOMLIST response = new ROOMLIST();
    String sql = persistence.getSQL(Persistence.KEY_FETCH_ROOMLIST);
    logSQLKey(Persistence.KEY_FETCH_ROOMLIST, sql);

    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql)
    ) {

      ArrayList<Room> roomarray = new ArrayList<>();
      // mapping auf deutsche bezeichnungen
      while (rs.next()) {
        Room room = new Room();
        room.setChatid(rs.getString(CHATID));
        switch(rs.getInt(ENTRY)) {
          case 1:
            room.setEntry(Entry.PUBLIC);
            break;
          case 2:
            room.setEntry(Entry.PROTECTED);
            break;
          default:
            log.fatal(
                "ROOMLIST kann nicht gesendet werden, weil ENTRY einen unbekannten oder unzulässigen Typ enthält."
            );
            throw new DatabaseException(
                "ROOMLIST kann nicht gesendet werden, weil ENTRY einen unbekannten oder unzulässigen Typ enthält."
            );
        }

        switch(rs.getInt(LIFETIME)) {
          case 1:
            room.setLifetime(Lifetime.PERMANENT);
            break;
          case 2:
            room.setLifetime(Lifetime.TEMPORARY);
            break;
          default:
            throw new DatabaseException(
                "ROOMLIST kann nicht gesendet werden, weil LIFETIME einen unbekannten oder unzulässigen Typ enthält."
            );
        }
        room.setOwner(rs.getString("OWNER"));
        // roomtype oder ort eintragen
        room.setRoomtype(Roomtype.toRoomtype(rs.getInt(ROOMTYPE)));
        roomarray.add(room);
      }
      response.setRoom(roomarray.toArray(new Room[roomarray.size()]));
      response.setHeader(RESPONSE);
      response.setCommand(Command.ROOMLIST);
      response.setDataset(Protocol.DATASET);
    }
    catch (SQLException e) {
      response.setCommand(Command.ROOMLIST);
      response.setHeader(ERROR);
      response.setDataset(Protocol.DATASET);
      log.warn(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }
    return response;
  }



  /**
   * Finde alle Räume, in in denen der übergebene User online ist.
   *
   * @param session
   *                die Session bezieht sich auf einen User
   */
  public List<String> fetchRoomlist(String session) {
    ArrayList<String> roomlist = new ArrayList<>(3);
    String sql = persistence.getSQL(Persistence.KEY_FETCH_ONLINE_ROOMLIST);
    logSQLKey(Persistence.KEY_FETCH_ONLINE_ROOMLIST, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setString(1, session);
      try(ResultSet rs = statement.executeQuery()) {
        while (rs.next()) {
          roomlist.add(rs.getString(CHATID));
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return roomlist;
  }



  /**
   * Gib eine Liste aller Räume zurück, wenn der User Mitglied ist und der Raum
   * vom Typ = [FORUM, BESPRECHUNGSRAUM, PAUSENRAUM, GRUPPENRAUM] ist.
   *
   *
   * @param userid
   *               ein Anwender
   * @param type
   *               [FORUM, BESPRECHUNGSRAUM, PAUSENRAUM, GRUPPENRAUM]
   * @return eine Raumliste
   */
  public List<String> fetchRooms(String userid, Roomtype type) {
    ArrayList<String> rooms = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_READ_ROOMS);
    logSQLKey(Persistence.KEY_READ_ROOMS, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, userid);
      statement.setInt(2, type.getRoomtype());
      try(ResultSet rs = statement.executeQuery()) {
        while (rs.next()) {
          String chatid = rs.getString(1);
          rooms.add(chatid);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return rooms;
  }



  /**
   * Ermittle den Raumtyp (STAMMTISCH, PAUSENRAUM, FORUM) für den übergebenen
   * CHAT.
   *
   *
   * @param chatid
   * @return STAMMTISCH, FORUM oder PAUSENRAUM
   */
  public int fetchRoomtype(String chatid) {
    int type = 0;
    String sql = persistence.getSQL(Persistence.KEY_ROOMTYPE);
    logSQLKey(Persistence.KEY_ROOMTYPE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql);
    ) {
      prepstatement.setString(1, chatid);
      try(ResultSet rs = prepstatement.executeQuery()) {
        if (rs.next()) {
          type = rs.getInt(1);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return type;
  }



  /**
   * Hole den RSA Public Key.
   * 
   * @return dieser Public Key oder {@code null}, wenn keiner vorhanden ist
   */
  public String fetchRsaPrivateKey() {
    String sql = persistence.getSQL(Persistence.KEY_FETCH_RSA_PRIVATE);
    logSQLKey(Persistence.KEY_FETCH_RSA_PRIVATE, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql);
    ) {
      return rs.next() ? rs.getString(1) : null;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Hole den RSA Public Key. Der Public Key ist Base64 codiert.
   * 
   * @return dieser Public Key oder {@code null}, wenn keiner vorhanden ist
   */
  public String fetchRsaPublicKey() {
    String sql = persistence.getSQL(Persistence.KEY_FETCH_RSA_PUBLIC);
    logSQLKey(Persistence.KEY_FETCH_RSA_PUBLIC, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql);
    ) {
      return rs.next() ? rs.getString(1) : null;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   *
   * Hole alle User mit ihrer SESSION_Id
   *
   * @param room
   * @return
   */
  public List<String> fetchSessionlist(String room) {
    ArrayList<String> sessionlist = new ArrayList<>(10);
    String sql = persistence.getSQL(Persistence.KEY_FETCH_SESSIONLIST);
    logSQLKey(Persistence.KEY_FETCH_SESSIONLIST, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql);
    ) {
      prepStatement.setString(1, room);
      try(ResultSet rs = prepStatement.executeQuery();) {
        while (rs.next()) {
          sessionlist.add(rs.getString(SESSION));
        }
      }
    }
    catch (SQLException e) {
      log.warn(e.getMessage(), e.getCause());
    }
    return sessionlist;
  }



  /**
   * Der Lautsprechername wird ausgelesen.
   *
   *
   * @param mail
   *             der Lautsprechername wird über die EMail-Adresse ausgelesen
   * @return der Lautsprechername oder {@code null}
   */
  public String fetchSpeaker(String mail) {
    String sql = persistence.getSQL(Persistence.KEY_FETCH_SPEAKER);
    logSQLKey(Persistence.KEY_FETCH_SPEAKER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {

      prepStatement.setString(1, mail);
      try(ResultSet rs = prepStatement.executeQuery()) {
        if (rs.next()) return rs.getString(1);
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return null;
  }



  /**
   * Hole alle Pausenräume und Gruppenräume.
   * 
   * @return alle gefundenen Räume oder eine leere Liste
   * 
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   */
  public List<String> fetchTemproomlist() throws DatabaseException {
    ArrayList<String> temparray = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_FETCH_TEMPROOMS);
    logSQLKey(Persistence.KEY_FETCH_TEMPROOMS, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql)
    ) {

      while (rs.next()) {
        temparray.add(rs.getString(CHATID));
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage());
      throw new DatabaseException(e.getMessage());
    }
    return temparray;
  }



  public TOPICMEMBER fetchTopicmember(TOPICMEMBER request) {
    TOPICMEMBER response = new TOPICMEMBER();
    String sql = persistence.getSQL(Persistence.KEY_TOPICMEMBER);
    logSQLKey(Persistence.KEY_TOPICMEMBER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql);
    ) {
      prepstatement.setString(1, request.getRoom());
      prepstatement.setString(2, request.getUserid());
      try(ResultSet rs = prepstatement.executeQuery();) {
        ArrayList<ChatUser> memberarray = new ArrayList<>();
        while (rs.next()) {
          ChatUser room = new ChatUser();
          room.setNickname(rs.getString("NICKNAME"));
          room.setUserid(rs.getString("UID"));
          memberarray.add(room);
        }

        // roomtype muss extra geholt werden
        // chatid==room habe ich

        response.setHeader(RESPONSE);
        response.setCommand(Command.TOPICMEMBER);
        response.setDataset(Protocol.DATASET);
        response.setUserid(request.getUserid());
        response.setRoom(request.getRoom());
        response.setRoomtype(Roomtype.toRoomtype(fetchRoomtype(request.getRoom())));

        log.info(memberarray.size());

        response.setChatUser(memberarray.toArray(new ChatUser[memberarray.size()]));

      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return response;
  }



  public READTOPICROOMOWNER fetchTopicroom(READTOPICROOMOWNER request) throws DatabaseException {
    READTOPICROOMOWNER response = new READTOPICROOMOWNER();
    String sql = persistence.getSQL(Persistence.READ_TOPICROOM_OWNER);
    logSQLKey(Persistence.READ_TOPICROOM_OWNER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql);
    ) {
      prepstatement.setString(1, request.getOwner());
      try(ResultSet rs = prepstatement.executeQuery()) {
        ArrayList<Room> roomarray = new ArrayList<>();
        while (rs.next()) {
          Room room = new Room();
          room.setChatid(rs.getString(CHATID));
          room.setEntry(Entry.toEntry(rs.getInt(ENTRY)));
          switch(rs.getInt(LIFETIME)) {
            case 1:
              room.setLifetime(Lifetime.PERMANENT);
              break;
            case 2:
              room.setLifetime(Lifetime.PERMANENT);
              break;
            default:
              throw new DatabaseException(
                  "READTOPICROOMOWNER kann nicht gesendet werden, weil LIFETIME einen unbekannten oder unzulässigen Typ enthält."
              );
          }
          roomarray.add(room);
        }
        response.setHeader(RESPONSE);
        response.setCommand(Command.READTOPICROOMOWNER);
        response.setDataset(Protocol.DATASET);
        response.setOwner(request.getOwner());
        response.setUserid(request.getUserid());
        response.setRoom(roomarray.toArray(new Room[roomarray.size()]));
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return response;
  }



  /**
   *
   * Ermittle alle private Besprechungsräume.
   *
   * @return alle Besprechungsräume
   * 
   * @throws SQLException
   *                      Befehl konnte nicht ausgeführt werden
   */
  public List<String> fetchTopicrooms() throws SQLException {
    ArrayList<String> rooms = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_READ_TOPICROOMS);
    logSQLKey(Persistence.KEY_READ_TOPICROOMS, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
    ) {
      try(ResultSet rs = statement.executeQuery(sql)) {
        while (rs.next()) {
          String chatid = rs.getString(1);
          rooms.add(chatid);
        }
      }
    }
    return rooms;
  }



  /**
   * Alle Attribute eines Anwenders auslesen. Die Tabelle TB_USER wird gelesen.
   * Der Rückgabewert kann {@code null} sein, wenn der Anwender nicht gefunden
   * wurde.
   *
   * @param userid
   *               ein Anwender
   * @return alle Attribute
   */
  public EntityUser fetchUser(String userid) {
    EntityUser user = null;
    String sql = persistence.getSQL(Persistence.KEY_FETCH_USER);
    logSQLKey(Persistence.KEY_FETCH_USER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {
      prepstatement.setString(1, userid);

      try(ResultSet rs = prepstatement.executeQuery()) {
        if (rs.next()) {
          user = new EntityUser();
          user.setUid(userid);
          user.setBenutzerstatus(rs.getInt("BENUTZERSTATUS"));
          user.setMail(rs.getString(MAIL));
          user.setNickname(rs.getString(NICKNAME));
          user.setForeground(rs.getInt(FOREGROUND));
          user.setBackground(rs.getInt(BACKGROUND));
          user.setVolume(rs.getInt("VOLUME"));
          user.setIptvvolume(rs.getInt("IPTVVOLUME"));
          user.setPlayervolume(rs.getInt("PLAYERVOLUME"));
          user.setOncall(rs.getBoolean("ONCALL"));
          user.setTelefonpuffer(rs.getInt("TELEFONPUFFER"));
          user.setSpeaker(rs.getString("SPEAKER"));
          user.setMikrofon(rs.getString("MIKROFON"));
          user.setChatdenied(rs.getBoolean("CHATDENIED"));
          user.setChattooltip(rs.getBoolean("CHATTOOLTIP"));
          user.setChatDownloadDir(rs.getString("CHATDOWNLOADDIR"));
          user.setChatUploadDir(rs.getString("CHATUPLOADDIR"));
          user.setOnrekord(rs.getBoolean("ONREKORD"));
          user.setOnprojektor(rs.getBoolean("ONPROJEKTOR"));
          user.setEis(rs.getInt("EIS"));
          user.setLanguage(Language.toLanguage(rs.getString(LANGUAGE)));
          user.setLdelete(rs.getBoolean(LDELETE));
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return user;
  }



  /**
   * Mit welchem Gerät hatte sich der Anwender angemeldet?
   * 
   * @param userid
   *               dieser Anwender
   * @return USER-AGENT ist Deskop, Browser, Tablet oder SmartPhone
   * 
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht verarbeitet werden
   */
  public Integer fetchUserAgent(String userid) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_FETCH_AGENT);
    logSQLKey(Persistence.KEY_FETCH_AGENT, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepareStatement = connection.prepareStatement(sql)
    ) {

      prepareStatement.setString(1, userid);
      try(ResultSet rs = prepareStatement.executeQuery()) {
        if (rs.next()) {
          return rs.getInt(AGENT);
        }
        throw new DatabaseException("generischer Fehler");
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }

  }



  /**
   * Hole die Verbotsliste vom Anwender XYZ
   *
   * @param userid
   *               die Userid
   * @return alle Verbotsendungen
   */
  public List<String> fetchUserBlacklist(String userid) {
    ArrayList<String> list = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_FETCH_USER_BLACKLIST);
    logSQLKey(Persistence.KEY_FETCH_USER_BLACKLIST, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepareStatement = connection.prepareStatement(sql)
    ) {

      prepareStatement.setString(1, userid);
      try(ResultSet rs = prepareStatement.executeQuery()) {
        while (rs.next()) {
          list.add(rs.getString(SUFFIX));
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return list;
  }



  /**
   * Hole die Benutzerdaten über die Mailadresse ab.
   * 
   * @param mail
   *             diese Mailadresse
   * 
   * @return diese Benutzerdaten oder {@code null}
   */
  public EntityUser fetchUserByMail(String mail) {
    EntityUser user = null;
    String sql = persistence.getSQL(Persistence.KEY_SIGNIN_PRESENT);
    logSQLKey(Persistence.KEY_SIGNIN_PRESENT, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {

      statement.setString(1, mail);

      try(ResultSet rs = statement.executeQuery()) {
        if (rs.next()) {
          user = new EntityUser();
          user.setUid(rs.getString(UID));
          user.setMail(rs.getString(MAIL));
          user.setNickname(rs.getString(NICKNAME));
          user.setBenutzerstatus(rs.getInt(BENUTZERSTATUS));
          user.setConfirmation_flag(rs.getInt(CONFIRMATION_FLAG));
          user.setForeground(rs.getInt(FOREGROUND));
          user.setBackground(rs.getInt(BACKGROUND));
          user.setLanguage(Language.toLanguage(rs.getString(LANGUAGE)));
        }
        return user;
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }

  }



  /**
   * Über den Nickname wird die Userid ermittelt.
   * 
   * @param remoteNickname
   *                       dieser Nickname
   * @return die zugehörige userid
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht verarbeitet werden
   */
  public String fetchUserByNickname(String remoteNickname) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_UID_BY_NICKNAME);
    logSQLKey(Persistence.KEY_UID_BY_NICKNAME, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepareStatement = connection.prepareStatement(sql)
    ) {

      prepareStatement.setString(1, remoteNickname);
      try(ResultSet rs = prepareStatement.executeQuery()) {
        if (rs.next()) {
          return rs.getString(UID);
        }
        throw new DatabaseException(remoteNickname + " - Nickname nicht gefunden");
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }

  }



  /**
   * Hole alle User, die gerade online sind, außer den Aufrufer. Maximal werden
   * 256 Anwender gelesen.
   *
   * @param uid
   *            UserID
   * @return alle User mit IP, NICKNAME und UID
   */
  public USERONLINELIST fetchUseronlinelist(String uid) {
    USERONLINELIST useronlinelist = new USERONLINELIST();
    useronlinelist.setCommand(Command.USERONLINELIST);
    useronlinelist.setHeader(RESPONSE);
    useronlinelist.setDataset(Protocol.DATASET);
    useronlinelist.setUserid(uid);
    String sql = persistence.getSQL(Persistence.KEY_FETCH_USERONLINELIST);
    logSQLKey(Persistence.KEY_FETCH_USERONLINELIST, sql);

    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
    ) {
      ArrayList<UserOnline> onlinelist = new ArrayList<>();
      try(ResultSet rs = statement.executeQuery(sql)) {
        int index = 0;
        while (rs.next() && index < MAX_USER_ONLINE) {
          index++;
          UserOnline online = new UserOnline();
          online.setNickname(rs.getString(NICKNAME));
          online.setUserid(rs.getString(UID));
          onlinelist.add(online);
        }
        useronlinelist.setUserOnline(onlinelist.toArray(UserOnline[]::new));
      }
    }
    catch (SQLException e) {
      log.error(e);
      throw new DatabaseException(e.getMessage());
    }
    return useronlinelist;
  }



  /**
   * Alle Benutzer werden mit ihren Attributen ausgelesen.
   *
   * @return alle Benutzer
   *
   * @throws SQLException
   *                      der SQL-Befehl konnte nicht ausgeführt werden
   */
  public List<EntityUser> fetchUsers() throws SQLException {
    EntityUser user = null;
    ArrayList<EntityUser> users = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_FETCH_USERS);
    logSQLKey(Persistence.KEY_FETCH_USERS, sql);
    try(
      Connection connection = pool.getConnection();
      Statement prepstatement = connection.createStatement();
      ResultSet rs = prepstatement.executeQuery(sql)
    ) {

      while (rs.next()) {
        user = new EntityUser();
        user.setUid(rs.getString(UID));
        user.setMail(rs.getString(MAIL));
        user.setNickname(rs.getString(NICKNAME));
        user.setLdelete(rs.getBoolean(LDELETE));
        user.setTime(ZonedDateTime.of(rs.getTimestamp(TIME).toLocalDateTime(), ZoneId.of(TIMEZONE)));
        users.add(user);
      }
    }
    return users;
  }



  /**
   * Finde den Videosender über seinen Empfänger. Der Empfänger muss immer einen
   * Sender haben.
   * 
   * @param receiver
   *                 dieser Empfänger
   * 
   * @return dieser Sender oder {@code null}, wenn kein Sender gefunden wurde
   */
  public ViewSenderSession fetchVideosenderByReceiver(String receiver) {
    ViewSenderSession view = null;
    String sql = persistence.getSQL(Persistence.KEY_FETCH_VIDEO_USER_BY_RECEIVER);
    logSQLKey(Persistence.KEY_FETCH_VIDEO_USER_BY_RECEIVER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql);
    ) {
      prepstatement.setString(1, receiver);
      try(ResultSet rs = prepstatement.executeQuery()) {
        if (rs.next()) {
          view = new ViewSenderSession();
          view.setSender(rs.getString(SENDER));
          view.setSession(rs.getString(SESSION));
        }
        return view;
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Filtere wirklich neue Teilnehmer aus der Positivliste heraus. Diese neuen
   * Teilnehmer hatten bsiher keinen Zutritt zur Telefonkonferenz.
   *
   *
   * @param konferenzraum
   *                         der Konferenzname
   * @param teilnehmerUserid
   *                         eine Positivliste
   *
   * @return alle neuen Teilnehmer
   * @throws SQLException
   */
  List<String> findeNeueTeilnehmer(String konferenzraum, Collection<String> teilnehmerUserid)
      throws SQLException {

    ArrayList<String> uid = new ArrayList<>();
    final String union = " union ";
    StringBuilder buffer = new StringBuilder();
    for (String teilnehmer : teilnehmerUserid) {
      buffer.append("select '").append(teilnehmer).append("' as UID from dual").append(union);
    }
    String sql = persistence.getSQL(Persistence.KEY_FINDE_TEILNEHMER);
    sql = sql.replace("XXX", Util.stripTrailing(buffer.toString(), union));
    logSQLKey(Persistence.KEY_FINDE_TEILNEHMER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setString(1, konferenzraum);
      try(ResultSet rs = prepstatement.executeQuery()) {
        while (rs.next()) {
          uid.add(rs.getString("t1.UID"));
        }
      }
    }
    return uid;
  }



  /**
   * Finde den Telefonpartner von {@code uid}.
   *
   *
   * @param uid
   *            die userid
   *
   * @return die Partner-userid oder {@code null} für nicht gefunden
   */
  public String findPartnerUserid(String uid) {
    String sql = persistence.getSQL(Persistence.KEY_FIND_PARTNER_USERID);
    String caller_id = null;
    String receiver_id = null;
    logSQLKey(Persistence.KEY_FIND_PARTNER_USERID, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, uid);
      statement.setString(2, uid);
      try(ResultSet rs = statement.executeQuery()) {
        if (rs.next()) {
          caller_id = rs.getString("CALLER_UID");
          receiver_id = rs.getString("RECEIVER_UID");
        }
        else {
          return null;
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return caller_id.equals(uid) ? receiver_id : caller_id;
  }



  /**
   * Finde alle Dateiendungen, für die der Anwender noch keine Entscheidung
   * getroffen hat, ob sie gesperrt sind oder nicht.
   *
   * @param userid
   *               die Userid
   *
   * @return für diesen Anwender alle nicht zugeordneten Dateiendungen
   */
  public List<String> findSuffixes(String userid) {
    ArrayList<String> suffix = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_SUFFIXES);
    logSQLKey(Persistence.KEY_UPDATE_SUFFIXES, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, userid);

      try(ResultSet rs = statement.executeQuery()) {
        while (rs.next()) {
          suffix.add(rs.getString("TB_FILETYPES.SUFFIX"));
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return suffix;
  }



  public int forumCycle() {
    String sql = persistence.getSQL(Persistence.KEY_FORUM_CHECK);
    if (log.isDebugEnabled()) {
//      logSQLKey(Persistence.KEY_FORUM_CHECK, sql);
    }
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql)
    ) {
      if (rs.next()) {
        return rs.getInt(1);
      }
      else {
        return 150;
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);

    }
    return 150;
  }



  /**
   * Wie viele User sind gerade online?
   *
   *
   * @return die Anzahl der User, die online sind
   */
  public int getCountOnline() {
    int count = 0;
    String sql = persistence.getSQL(Persistence.KEY_COUNT_ONLINE);
    logSQLKey(Persistence.KEY_COUNT_ONLINE, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql)
    ) {
      if (rs.next()) {
        count = rs.getInt(1);
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return count;
  }



  public abstract PoolProperties getDefaultPoolProperties() throws SAXException, JAXBException;



  /**
   * Welche Lebensdauer hat der Raum?
   * 
   * @param room
   *             dieser Chatraum
   * 
   * @return diese Lebenszeit
   * @throws DatabaseException
   *                           unbekannte Lebensdauer
   */
  public Lifetime getLifetime(String room) throws DatabaseException {
    Lifetime lifetime = null;
    String sql = persistence.getSQL(Persistence.KEY_FETCH_LIFETIME);
    logSQLKey(Persistence.KEY_FETCH_LIFETIME, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql);
    ) {
      prepStatement.setString(1, room);
      try(ResultSet rs = prepStatement.executeQuery();) {
        if (rs.next()) {
          try {
            lifetime = Lifetime.toLifetime(rs.getInt(LIFETIME));
          }
          catch (IllegalArgumentException e) {
            throw new DatabaseException("1 - Lifetime muss TEMPORARY oder PERMANENT sein");
          }
        }
        else {
          throw new DatabaseException("2 - Lifetime muss TEMPORARY oder PERMANENT sein");
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }
    return lifetime;
  }



  /**
   * Lies alle projektorbezogenen Daten. Der Antwortdatensatz ist immer definiert,
   * kann aber leer sein.
   *
   * @param callerSession
   *                      die Session vom Sender
   *
   * @return alle projektorbezogenen Daten
   * 
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   */
  public Screencast getScreencastByCallerSession(String callerSession) {
    Screencast screencast = new Screencast();
    String sql = persistence.getSQL(Persistence.KEY_SCREENCAST_BY_CALLERSESSION);
    logSQLKey(Persistence.KEY_SCREENCAST_BY_CALLERSESSION, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {

      prepStatement.setString(1, callerSession);
      try(ResultSet rs = prepStatement.executeQuery()) {
        if (rs.next()) {
          screencast.setReceiverSession(rs.getString("RECEIVERSESSION"));
          screencast.setSenderSession(callerSession);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
    return screencast;
  }



  /**
   * Lies alle projektorbezogenen Daten. Der Antwortdatensatz ist immer definiert,
   * kann aber leer sein.
   *
   * @param receiverSession
   *                        die Session vom Empfänger, auf dem der Projektor läuft
   *
   * @return alle projektorbezogenen Daten
   */
  public Screencast getScreencastByReceiverSession(String receiverSession) {
    Screencast screencast = new Screencast();
    String sql = persistence.getSQL(Persistence.KEY_SCREENCAST_BY_RECEIVERSESSION);
    logSQLKey(Persistence.KEY_SCREENCAST_BY_RECEIVERSESSION, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {

      prepStatement.setString(1, receiverSession);
      try(ResultSet rs = prepStatement.executeQuery()) {
        if (rs.next()) {
          screencast.setReceiverSession(receiverSession);
          screencast.setSenderSession(rs.getString("SENDERSESSION"));
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return screencast;
  }



  /**
   * Die SessionId kann über einen Nickname ermittelt werden.
   *
   * @param nickname
   *                 ein Nickname
   * @return die Sessionid oder {@code null}
   */
  public String getSessionidByNickname(String nickname) {
    String sql = persistence.getSQL(Persistence.SESSIONID_BY_NICKNAME);
    logSQLKey(Persistence.SESSIONID_BY_NICKNAME, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, nickname);
      try(ResultSet rs = statement.executeQuery()) {
        if (rs.next()) {
          return rs.getString(SESSION);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return null;
  }



  /**
   * Die SessionId kann über die Userid ermittelt werden.
   *
   *
   * @param uid
   *            die Userid
   * @return die Sessionid oder {@code null}
   */
  public String getSessionidByUserid(String uid) {
    String sql = persistence.getSQL(Persistence.KEY_SESSIONID_BY_USERID);
    logSQLKey(Persistence.KEY_SESSIONID_BY_USERID, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {

      statement.setString(1, uid);
      try(ResultSet rs = statement.executeQuery()) {
        if (rs.next()) {
          return rs.getString(SESSION);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return null;
  }



  /**
   * Hole das {@code mail.smtp.host} Property aus der {@code context.xml}.
   * 
   * @return dieser Host
   * 
   * @throws NamingException
   *                         das Host Property wurde nicht gefunden
   */
  public abstract String getSmtpHost() throws NamingException;

  /**
   * Hole das {@code mail.smtp.password} aus der {@code context.xml}.
   * 
   * @return dieses Passwort
   * 
   * @throws NamingException
   *                         das Passwort wurde nicht gefunden
   */
  public abstract String getSmtpPassword() throws NamingException;

  /**
   * Hole das {@code mail.smtp.port} Property aus der {@code context.xml}.
   * 
   * @return dieser Port
   * @throws NamingException
   */
  public abstract Integer getSmtpPort() throws NamingException;

  /**
   * Hole das {@code mail.smtp.user} Property aus der {@code context.xml}.
   * 
   * @return dieser User
   * @throws NamingException
   */
  public abstract String getSmtpUser() throws NamingException;



  /**
   * Die Userid wird über die Sessionid ermittelt. Der Anwender muss online sein.
   *
   * @param sessionid
   *                  die SessionId aus der Tabelle TB_ONLINE
   *
   * @return die Userid oder {@code null}, wenn keiner gefunden wurde
   */
  public String getUserBySession(String sessionid) {
    String sql = persistence.getSQL(Persistence.KEY_FETCH_USER_BY_SESSIONID);
    // Der SQL wird aus Gescwindigkeitsgründen bei der Sprachübertragung nicht
    // geloggt
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {

      prepStatement.setString(1, sessionid);
      try(ResultSet rs = prepStatement.executeQuery()) {
        if (rs.next()) {
          return rs.getString(1);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
    return null;
  }



  /**
   * Erteile dem Anwender eine Zugriffsberechtigung für ein Modul.
   *
   *
   * @param userid
   *               ein Anwender
   * @param module
   *               ein Modul
   */
  public void grantModule(String userid, int module) {
    String sql = persistence.getSQL(Persistence.KEY_GRANT_MODULE);
    logSQLKey(Persistence.KEY_GRANT_MODULE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      prepStatement.setString(1, userid);
      prepStatement.setInt(2, module);
      prepStatement.executeUpdate();
    }
    catch (SQLException e) {
      log.warn(e.getMessage(), e.getCause());
    }
  }



  /**
   * Die aktuellste Nachricht liegt oben auf dem Stapel.
   *
   *
   * @param room
   *             alle Nachrichten werden aus diesem Chatraum gelesen
   * @return alle Nachrichten
   */
  Stack<Attachment> historymessage(String room) {
    Stack<Attachment> stack = new Stack<>();
    String sql = persistence.getSQL(Persistence.KEY_FETCH_HISTORYMESSAGE);
    logSQLKey(Persistence.KEY_FETCH_HISTORYMESSAGE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, room);
      try(ResultSet rs = statement.executeQuery()) {
        while (rs.next()) {
          Attachment record = new AttachmentImpl();
          Timestamp timestamp = rs.getTimestamp("TB_RECORD.TIME");
          ZonedDateTime tz = timestamp.toLocalDateTime().atZone(ZoneId.of(TIMEZONE));
          long value = tz.toInstant().toEpochMilli();
          record.setDatetime(value);
          record.setMessage(rs.getString("MESSAGE"));
          record.setAttachment(rs.getObject("TB_RECORD.ANLAGE", Long.class));
          record.setFilename(rs.getString("FILENAME"));
          record.setFilesize(rs.getObject(FILESIZE, Long.class));
          record.setRoom(room);
          ChatUser chatuser = new ChatUser();
          chatuser.setBackgroundColor(rs.getInt(BACKGROUND));
          chatuser.setForegroundColor(rs.getInt(FOREGROUND));
          chatuser.setNickname(rs.getString("NICKNAME"));
          chatuser.setUserid(rs.getString("TB_RECORD.UID"));
          record.setChatUser(chatuser);
          stack.push(record);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return stack;
  }



  /**
   *
   * Wie lang ist die HISTORYMESSAGE?
   *
   * @param CHATROOM
   *                 der Chatraum
   * @param dataset
   *                 die Records enthalten die bisherigen Nachrichten
   * @return die Länge der HISTORYMESSAGE, gäbe es nur die übergebenen datasets
   * @deprecated wird nicht aufgerufen
   */
  @Deprecated
  int historyMessageLen(String room, List<net.javacomm.protocol.RecordInterface> dataset) {
    HISTORYMESSAGE response = new HISTORYMESSAGE();
    response.setCommand(Command.HISTORYMESSAGE);
    response.setHeader(RESPONSE);
    response.setDataset(Protocol.DATASET);
    response.setRoom(room);
    response.setAttachment(dataset.toArray(new AttachmentImpl[dataset.size()]));
    MessageEncoder encoder = new MessageEncoder();
    String text = "";
    try {
      text = encoder.encode(response);
    }
    catch (EncodeException e) {
      log.fatal(e.getMessage(), e.getCause());
    }
    return text.length();
  }



  /**
   * lässt die Initialisierung beim Programmstart aus
   */
  public abstract void init();



  /**
   * Eine neue Nachricht wird gespeichert. CHATID,UID,ANLAGE,MESSAGE
   * 
   * @param chatid
   *                dieser Raum
   * @param uid
   *                dieser Anwender hat die Nachricht geschickt
   * @param anlage
   *                diese Datei wurde der Nachricht angeheftet
   * @param message
   *                diese Nachricht wurde gesendet
   * 
   * @return dieser Datensatz wurde gespeichert
   * 
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   */
  public net.javacomm.database.entity.Record insertChatmessage(String chatid, String uid, Long anlage,
      String message) throws DatabaseException {

    Record record = new net.javacomm.database.entity.Record();
    if (anlage == null) {
      Matcher matcher = EOT4.matcher(message);
      if (matcher.matches()) {
        throw new DatabaseException("EOT4 in der Nachricht");
      }
    }
    String sql = persistence.getSQL(Persistence.KEY_INSERT_CHATMESSAGE);
    logSQLKey(Persistence.KEY_INSERT_CHATMESSAGE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setString(1, chatid);
      statement.setString(2, uid);
      statement.setObject(3, anlage, Types.BIGINT); // mit setObject kann null gesetzt werden
      statement.setString(4, message);
      statement.executeUpdate();

      // Zeit aus der Datenbank
      // Number aus der Datenbank

      record.setChatid(chatid);
      record.setUid(uid);
      record.setAnlage(anlage);
      record.setMessage(message);
      return record;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException("Diese Nachricht konnte nich gespeichert werden");
    }

  }



  /**
   * Ein Chunk wird in die Datenbank eingetragen.
   * 
   * @param anlage
   *                dieser Schlüssel verweist auf eine Datei.
   * @param chunk
   *                die Chunknummer
   * @param content
   *                dieser Dateiinhalt
   * 
   * @return die übertragenden Bytes
   * 
   * @throws DatabaseException
   *                           der übergebene Chunk löste einen
   *                           Verarbeitungsfehler aus
   */
  public int insertChunk(Long anlage, int chunknummer, byte[] content) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_INSERT_CHUNKS);
    logSQLKey(Persistence.KEY_INSERT_CHUNKS, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql);
    ) {
      prepstatement.setLong(1, anlage);
      prepstatement.setInt(2, chunknummer);
      ByteArrayInputStream stream = new ByteArrayInputStream(content, 0, content.length);
      prepstatement.setBlob(3, stream);
      prepstatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
    return content.length;
  }



  /**
   * Ein Chunk wird in die Datenbank eingetragen.
   * 
   * @param anlage
   *                    dieser Schlüssel verweist auf eine Datei.
   * @param chunknummer
   *                    die Chunknummer
   * @param is
   *                    der Dateiinhalt als Stream
   * 
   * @return die übertragenden Bytes
   * 
   * @throws DatabaseException
   *                           der übergebene Chunk löste einen
   *                           Verarbeitungsfehler aus
   * 
   * @throws IOException
   *                           der Stream kann nicht verarbeitet werden
   */
  public int insertChunk(Long anlage, Integer chunknummer, InputStream is)
      throws DatabaseException, IOException {
    return insertChunk(anlage, chunknummer, is.readAllBytes());
  }



  /**
   * Diese Methode wird beim Serverneustart ausgeführt. In der TB_CONFIG muss
   * genau ein Datensatz mit den Grundeinstellungen enthalten sein. Zu den
   * Grundeinstellungen gehören Verweildauer von Chatnachrichten, öffentliche
   * Domäne(ja/nein), Umwandlungszeit von Pausenraum nach Forum und ein
   * RSA-Schlüsselpaar.
   * 
   * 
   * @return bei {@code true} wurden Grundeinstellungen hinzugefügt, {@code false}
   *         war nicht nötig
   * 
   */
  public boolean insertConfigIf() {
    boolean done = false;
    String sqlBase = persistence.getSQL(Persistence.KEY_BASE_CONFIG);
    String sql = persistence.getSQL(Persistence.KEY_READ_CONFIG);
    logSQLKey(Persistence.KEY_READ_CONFIG, sql);
    try(

      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql);
      PreparedStatement prepstatement = connection.prepareStatement(sqlBase);
    ) {
      if (rs.next()) return done;
      KeyPair keypair = Crypto.createRSAGenerator();
      PublicKey publicKey = keypair.getPublic();
      PrivateKey privateKey = keypair.getPrivate();

      logSQLKey(Persistence.KEY_BASE_CONFIG, sqlBase);
      prepstatement.setBoolean(1, true);
      prepstatement.setInt(2, 150);
      prepstatement.setInt(3, 24);
      prepstatement.setString(4, Base64.getEncoder().encodeToString(publicKey.getEncoded()));
      prepstatement.setString(5, Base64.getEncoder().encodeToString(privateKey.getEncoded()));
      done = prepstatement.executeUpdate() >= 1;
    }
    catch (SQLException | NoSuchAlgorithmException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
    return done;
  }



  /**
   * Wenn ein Besprechungsraum aungelegt wurde, wird auch eine zugehörige Gruppe
   * angelegt. Der Einrichter ist immer das erste Gruppenmitglied. Der Gruppenname
   * ist identisch mit dem Raumnamen.
   * 
   * @param user
   *              dieser Anwender hat den Besprechungsraum angelegt und ist das
   *              erste Mitglied in der Gruppe
   * @param group
   *              der Gruppenname ist der Raumname
   * @return {@code true}, der Gruppeneintrag wurde vorgenommen
   */
  protected boolean insertGroupUser(String user, String group) {
    String sql = persistence.getSQL(Persistence.KEY_GROUP_USER);
    logSQLKey(Persistence.KEY_GROUP_USER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      prepStatement.setString(1, group);
      prepStatement.setString(2, user);
      prepStatement.executeUpdate();
      return true;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return false;
  }



  /**
   * Eine 1:1 Chatverbindung wird eingetragen.
   * 
   * @param remoteSessionID
   *                        dieser Empfänger
   * @param localSessionID
   *                        dieser Caller
   */
  public void insertPrivateChat(String remoteSessionID, String localSessionID) {
    String sql = persistence.getSQL(Persistence.KEY_INSERT_SESSIONIDS);
    logSQLKey(Persistence.KEY_INSERT_SESSIONIDS, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {

      prepStatement.setString(1, remoteSessionID);
      prepStatement.setString(2, localSessionID);
      prepStatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Der PrivateChat speichert einen Datenblock/Chunk.
   * 
   * @param anlage
   *                    die Referenznummer auf die angehängte Datei
   * @param chunknummer
   *                    diese Datenblocknummer
   * @param content
   *                    dieser Datenblockinhalt
   * @return die Anzahl der gespeicherten Bytes
   * 
   * @throws DatabaseException
   *                           der übergebene Chunk löste einen
   *                           Verarbeitungsfehler aus
   */
  public int insertPrivateChunk(Long anlage, Integer chunknummer, byte[] content) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_INSERT_PRIVATE_CHUNKS);
    logSQLKey(Persistence.KEY_INSERT_PRIVATE_CHUNKS, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql);
    ) {
      prepstatement.setLong(1, anlage);
      prepstatement.setInt(2, chunknummer);
      ByteArrayInputStream stream = new ByteArrayInputStream(content, 0, content.length);
      prepstatement.setBlob(3, stream);
      prepstatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
    return content.length;

  }



  /**
   * Der PrivateChat speichert einen Datenblock/Chunk.
   * 
   * @param anlage
   *                    ie Referenznummer auf die angehängte Datei
   * @param chunknummer
   *                    diese Datenblocknummer
   * @param is
   *                    dieser Stream ist der zu speichernde Datenblock
   * 
   * @return die Anzahl der gespeicherten Bytes
   * 
   * @throws DatabaseException
   *                           der übergebene Chunk löste einen
   *                           Verarbeitungsfehler aus
   * @throws IOException
   *                           der Stream kann nicht verarbeitet werden
   */
  public int insertPrivateChunk(Long anlage, Integer chunknummer, InputStream is)
      throws DatabaseException, IOException {
    return insertPrivateChunk(anlage, chunknummer, is.readAllBytes());
  }



  /**
   * Der Anwender wird in ein Forum, Besprechungsraum, Gruppenraum oder Pausenraum
   * eingetragen.
   * 
   * @param userid
   *               dieser Anwender
   * @param room
   *               dieser Chatraum
   * 
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   * 
   */
  public void insertRoom(String userid, String room) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_USER_CHAT);
    logSQLKey(Persistence.KEY_USER_CHAT, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prep = connection.prepareStatement(sql)
    ) {
      prep.setString(1, userid);
      prep.setString(2, room);
      prep.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Trage alle neuen Teilnehmer aus der {@code Collection<String>} in die Tabelle
   * TB_KONFERENZRAUM ein, die noch nicht in der Tabelle TB_KONFERENZRAUM stehen.
   *
   *
   * @param konferenzname
   *                         der Konferenzname
   * @param teilnehmerUserid
   *                         alle Konferenzmitglieder
   * @param organisator
   *                         der Organisator
   *
   * @return die Anzahl neuer Teilnehmer
   *
   * @throws SQLException
   *                      der SQL-Befehl konnte nicht verarbeitet werden
   */
  int insertTeilnehmer(String konferenzname, String organisator, Collection<String> teilnehmerUserid)
      throws SQLException {
    List<String> neueTeilnehmer = findeNeueTeilnehmer(konferenzname, teilnehmerUserid);
    int count = neueTeilnehmer.size();
    if (count == 0) return 0;
    final String trailing = ",";
    StringBuilder buffer = new StringBuilder();
    for (String teilnehmer : neueTeilnehmer) {
      buffer.append("('" + konferenzname + "', '" + organisator + "', '" + teilnehmer + "', '0'),");
    }
    String sql = persistence.getSQL(Persistence.KEY_INSERT_NEUE_TEILNEHMER);
    sql = sql.replace("XXX", Util.stripTrailing(buffer.toString(), trailing));
    logSQLKey(Persistence.KEY_INSERT_NEUE_TEILNEHMER, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      statement.executeUpdate(sql);
    }
    return count;
  }



  /**
   * Ein Anwender wird in die Teilnehmerliste einer Telefonkonferenz aufgenommen.
   *
   * @param konferenzname
   *                      dieser Konferenzname
   * @param organisator
   *                      dieser Organisator hat die Konferenz eingerichtet
   * @param userid
   *                      dieser Teilnehmer
   */
  public void insertTelefonkonferenzraum(String konferenzname, String organisator, String userid) {
    String sql = persistence.getSQL(Persistence.KEY_INSERT_KONFERENZRAUM);
    logSQLKey(Persistence.KEY_INSERT_KONFERENZRAUM, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setString(1, konferenzname);
      prepstatement.setString(2, organisator);
      prepstatement.setString(3, userid);
      prepstatement.setBoolean(4, false);
      prepstatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
  }



  /**
   * Trage ein Einmal-Token in die Tabelle TB_TOKEN ein. Das Einmal-Token wird bei
   * einem USRLOGIN und UPDATE benötigt. Es ist Teil vom IDENTITY Attribut.
   * 
   * @param token
   *              dieses Token
   */
  public void insertToken(String token) {
    String sql = persistence.getSQL(Persistence.KEY_INSERT_TOKEN);
    logSQLKey(Persistence.KEY_INSERT_TOKEN, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepSender = connection.prepareStatement(sql);
    ) {
      prepSender.setString(1, token);
      prepSender.executeUpdate();

    }
    catch (SQLException e) {
      log.fatal(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Ein Telefonpaar, die miteinander telefonieren
   *
   *
   * @param caller_uid
   *                     der Anrufer
   * @param receiver_uid
   *                     der Empfänger
   */
  public void insertToPhone(String caller_uid, String receiver_uid) {
    String sql = persistence.getSQL(Persistence.KEY_INSERT_TOPHONE);
    logSQLKey(Persistence.KEY_INSERT_TOPHONE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, caller_uid);
      statement.setString(2, receiver_uid);
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  public int insertUploadfiles(String userid, String ip, int port, Uploadfile[] files) throws SQLException {
    List<String> blacklist = fetchUserBlacklist(userid);
    int count = 0;
    String sql = persistence.getSQL(Persistence.KEY_INSERT_UPLOADFILES);
    logSQLKey(Persistence.KEY_INSERT_UPLOADFILES, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      for (Uploadfile file : files) {
        if (Util.accessDenied(file.getFilename(), blacklist)) continue;
        prepStatement.clearParameters();
        prepStatement.setString(1, userid);
        prepStatement.setString(2, ip);
        prepStatement.setInt(3, port);
        prepStatement.setString(4, file.getFilename());
        Matcher matcher = PATH_SEPARATOR.matcher(file.getFilename());
        if (!matcher.matches()) continue;
        prepStatement.setString(5, matcher.group(1));
        prepStatement.setLong(6, file.getFilesize());

        int seq = transferlibNumber.getAndUpdate(v -> (v == Integer.MAX_VALUE ? 0 : v + 1));
        prepStatement.setInt(7, seq);

        prepStatement.executeUpdate();
        count++;
      }
    }
    return count;
  }



  /**
   * Trage einen ODX-Anwender in die Tabelle USER_ODX ein. Die Tabelle enthält
   * Anwenderattribute für das ODX-Modul.
   *
   *
   * @param userid
   *               ODX-Anwender
   */
  public void insertUserOdx(String userid) {
    String sql = persistence.getSQL(Persistence.KEY_INSERT_USER_ODX);
    logSQLKey(Persistence.KEY_INSERT_USER_ODX, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      prepStatement.setString(1, userid);
      prepStatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * 
   * Trage den Sender und den Empfänger einer Bildschirmübertragung in die Tabelle
   * TB_VIDEO ein.
   * 
   * @param senderUserid
   *                       dieser Anwender
   * 
   * @param receiverUserid
   *                       dieser Empfänger
   * 
   * @return {@code true}, der Anwneder wurde als Sender oder Empfänger in die
   *         Tabelle TB_VIDEO eingetragen
   * 
   */
  public void insertVideo(String senderUserid, String receiverUserid) {
    String sql = persistence.getSQL(Persistence.KEY_INSERT_SENDER_RECEIVER_VIDEO);
    logSQLKey(Persistence.KEY_INSERT_SENDER_RECEIVER_VIDEO, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepSender = connection.prepareStatement(sql);
    ) {
      prepSender.setString(1, senderUserid);
      prepSender.setString(2, receiverUserid);
      prepSender.executeUpdate();

    }
    catch (SQLException e) {
      log.fatal(e.getMessage(), e);
    }
  }



  /**
   * Trage nur den Versender von Bildschirmdaten in die Datenbank ein.
   * 
   * @param senderUserid
   *                     dieser Anwnender stellt das Bild ein
   */
  public void insertVideoSender(String senderUserid) {
    String sql = persistence.getSQL(Persistence.KEY_INSERT_VIDEO_SENDER);
    logSQLKey(Persistence.KEY_INSERT_VIDEO_SENDER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepSender = connection.prepareStatement(sql);
    ) {
      prepSender.setString(1, senderUserid);
      prepSender.executeUpdate();

    }
    catch (SQLException e) {
      log.fatal(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   *
   * Ist der Anwender ein Administrator?
   *
   * @param userid
   *                 eine Userid
   * @param password
   *                 das zugehörige Passwort
   * @return {@code true}, ist ein Administrator
   */
  public boolean isAdministrator(String userid, String password) {
    if (userid == null) return false;
    if (password == null) return false;
    String sql = persistence.getSQL(Persistence.KEY_ISADMIN);
    logSQLKey(Persistence.KEY_ISADMIN, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepareStatement = connection.prepareStatement(sql)
    ) {

      String hashedPassword = fetchPassword(userid, null);

      // Für Datenbanken, die keine MD5 Verschlüsselung haben
      boolean test = BCrypt.verifyer().verify(password.toCharArray(), hashedPassword).verified;
      if (!test) return false;

      prepareStatement.setString(1, userid);
      prepareStatement.setBoolean(2, true);
      try(ResultSet rs = prepareStatement.executeQuery()) {
        return rs.next();
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return false;
  }



  /**
   * Wenn dass ConfirmationFlag gesetzt ist und der confirmationKey passt, darf
   * das Password in der Datenbank geändert werden.
   *
   * @param userid
   *               die UserID
   * @return {@code true}, wenn das ConfirmationFlag gesetzt ist
   */
  public boolean isConfirmationkeyTrue(String userid, String confirmationKey) {
    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_CONFIRMATION_FLAG_PRESENT);
    logSQLKey(Persistence.KEY_CONFIRMATION_FLAG_PRESENT, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql);
    ) {
      prepstatement.setString(1, userid);
      prepstatement.setString(2, confirmationKey);
      try(ResultSet rs = prepstatement.executeQuery();) {
        if (rs.next()) {
          done = rs.getInt(1) == 1;
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return done;
  }



  /**
   * Ist der Raum verboten? Wenn ja, darf er nicht in TB_CHAT eingetragen werden?
   *
   * @param roomname
   *                 der zu prüfende Raumname
   * @return {@code true}, der Raum ist verboten
   */
  public boolean isForbiddenRoom(String roomname) {
    boolean done = true;
    String sql = persistence.getSQL(Persistence.KEY_FORBIDDEN_ROOM);
    logSQLKey(Persistence.KEY_FORBIDDEN_ROOMS, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql);
    ) {
      prepStatement.setString(1, roomname);
      try(ResultSet rs = prepStatement.executeQuery();) {
        done = rs.next();
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return done;
  }



  /**
   * Telefoniert gerade die Gegenseite?
   *
   * @param nickname
   * @return {@code false} die Gegenseite telefoniert nicht, {@code true} die
   *         Gegenseite telefoniert
   */
  public boolean isOnBusy(String nickname) {
    String sql = persistence.getSQL(Persistence.KEY_IS_ON_BUSY);
    logSQLKey(Persistence.KEY_IS_ON_BUSY, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, nickname);
      try(ResultSet rs = statement.executeQuery()) {
        if (!rs.next()) {
          return false;
        }
        return rs.getBoolean("ONBUSY");
      }

    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return false;
  }



  /**
   * Telefoniert gerade die Gegenseite?
   *
   * @param uid
   * @return {@code false} die Gegenseite telefoniert nicht, {@code true} die
   *         Gegenseite telefoniert
   */
  public boolean isOnBusyByUid(String uid) {
    String sql = persistence.getSQL(Persistence.KEY_IS_ON_BUSY_BY_UID);
    logSQLKey(Persistence.KEY_IS_ON_BUSY_BY_UID, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, uid);
      try(ResultSet rs = statement.executeQuery()) {
        if (!rs.next()) {

          log.info("Diesen User gibt es nicht mehr!");

          return false;
        }
        return rs.getBoolean("ONBUSY");
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return false;
  }



  /**
   * Möchte die Gegenseite angerufen werden?
   *
   * @param nickname
   *                 Nickname der Gegenseite
   * @return {@code false} die Gegenseite nimmt Anrufe entgegen, {@code true} die
   *         Gegenseite nimmt keine Anrufe entgegen
   */
  public boolean isOnCall(String nickname) {
    String sql = persistence.getSQL(Persistence.KEY_IS_ON_CALL);
    logSQLKey(Persistence.KEY_IS_ON_CALL, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, nickname);
      try(ResultSet rs = statement.executeQuery()) {
        if (!rs.next()) {
          return true;
        }
        return rs.getBoolean("ONCALL");
      }

    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return true;
  }



  /**
   * Ist der Empfänger in einer Sitzung?
   *
   * @param receiverSession
   *                        die SessionId ist eindeutig
   * @return {@code true}, er ist eingeschaltet
   */
  public boolean isOnProjektor(String receiverSession) {
    String sql = persistence.getSQL(Persistence.KEY_ON_PROJEKTOR);
    logSQLKey(Persistence.KEY_ON_PROJEKTOR, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {

      prepStatement.setString(1, receiverSession);
      try(ResultSet rs = prepStatement.executeQuery()) {
        return rs.next();
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return false;
  }



  /**
   * Ist der Benutzer online?
   *
   * @param session
   *                die Session eines Benutzers
   * @return {@code true}, der Anwender ist online
   */
  public boolean isOnrekord(String session) {
    String sql = persistence.getSQL(Persistence.KEY_ONLINE_ONRECORD);
    logSQLKey(Persistence.KEY_ONLINE_ONRECORD, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {
      prepstatement.setString(1, session);

      try(ResultSet rs = prepstatement.executeQuery()) {
        if (rs.next()) {
          return rs.getBoolean(1);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return false;
  }



  /**
   * Ist der Telefonierer der Anrufer?
   *
   *
   * @param uid
   *            die Userid
   * @return {@code true}, der Caller
   */
  public boolean isPartnerCaller(String uid) {
    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_CALLER_TOPHONE);
    logSQLKey(Persistence.KEY_CALLER_TOPHONE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, uid);
      try(ResultSet rs = statement.executeQuery()) {
        done = rs.next() ? true : false;
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return done;
  }



  /**
   * Überträgt der Anwender bereits Bildschirmdaten?
   * 
   * @param senderUserid
   *                     dieser Anwender
   * @return {@code true}, Übertragung läuft
   */
  public boolean isPresentVideosender(String senderUserid) {
    String sql = persistence.getSQL(Persistence.KEY_IS_PRESENT_VIDEOSENDER);
    logSQLKey(Persistence.KEY_IS_PRESENT_VIDEOSENDER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepSender = connection.prepareStatement(sql);
    ) {
      prepSender.setString(1, senderUserid);
      try(ResultSet rs = prepSender.executeQuery()) {
        return rs.next();
      }
    }
    catch (SQLException e) {
      log.fatal(e.getMessage(), e);
    }
    return false;
  }



  /**
   * Ist der Raum geschützt und nicht für jedermann zugänglich?
   * 
   * @param userid
   *               dieser Anwender
   * 
   * @param room
   *               dieser Raum
   * @return bei einem geschützten Raum wird der Raumtyp zurückgegeben, sonst null
   */
  public Roomtype isProtectedRoom(String userid, String room) {
    String sql = persistence.getSQL(Persistence.KEY_IS_PROTECTEDROOM);
    logSQLKey(Persistence.KEY_IS_PROTECTEDROOM, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement preparedStatement = connection.prepareStatement(sql);
    ) {
      preparedStatement.setString(1, userid);
      preparedStatement.setString(2, room);

      try(ResultSet rs = preparedStatement.executeQuery()) {
        return rs.next() ? Roomtype.toRoomtype(rs.getInt(ROOMTYPE)) : null;
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Ist der Raum öffentlich und für jedermann zugänglich?
   * 
   * @param room
   *             dieser Raum
   * @return bei einem öffentlichen Raum wird der Raumtyp zurückgegeben, sonst
   *         null
   */
  public Roomtype isPublicRoom(String room) {
    String sql = persistence.getSQL(Persistence.KEY_IS_PUBLICROOM);
    logSQLKey(Persistence.KEY_IS_PUBLICROOM, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement preparedStatement = connection.prepareStatement(sql);
    ) {
      preparedStatement.setString(1, room);
      try(ResultSet rs = preparedStatement.executeQuery()) {
        return rs.next() ? Roomtype.toRoomtype(rs.getInt(ROOMTYPE)) : null;
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Kein Empfänger darf Sender sein. Die Receiver Userid darf nicht in der Sender
   * Spalte enthalten sein.
   * 
   * @param receiverUserid
   *                       dieser Empfänger
   * @return {@code true}, wenn der Empfänger auch Sender ist
   * 
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   * 
   */
  public boolean isReceiverSender(String receiverUserid) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_IS_RECEIVER_SENDER);
    logSQLKey(Persistence.KEY_IS_RECEIVER_SENDER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, receiverUserid);
      try(ResultSet rs = statement.executeQuery()) {
        return rs.next();
      }
    }
    catch (SQLException e) {
      log.fatal(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Kein Sender darf Empfänger sein. Die Sender Userid darf nicht in der Receiver
   * Spalte enthalten sein.
   * 
   * @param senderUserid
   *                     dieser Sender
   * 
   * @return {@code true}, wenn der Sender auch Empfänger ist
   * 
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   */
  public boolean isSenderReceiver(String senderUserid) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_IS_SENDER_RECEIVER);
    logSQLKey(Persistence.KEY_IS_SENDER_RECEIVER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, senderUserid);
      try(ResultSet rs = statement.executeQuery()) {
        return rs.next();
      }
    }
    catch (SQLException e) {
      log.fatal(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }

  }



  /**
   * Wurde diese Telko angelegt?
   * 
   * @param konferenzraum
   *                      dieser Konferenzraum
   * @param organisator
   *                      dieser Organisator
   * 
   * @return {@code true}, wenn die Telko angelegt wurde
   * 
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   */
  public boolean isTelkoPresent(String konferenzraum, String organisator) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_FETCH_TELKO);
    logSQLKey(Persistence.KEY_FETCH_TELKO, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)

    ) {
      prepstatement.setString(1, konferenzraum);
      prepstatement.setString(2, organisator);
      try(ResultSet rs = prepstatement.executeQuery()) {
        return rs.next();
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Wurde der Anwender logisch gelöscht?
   *
   * @param userid
   *               ein Anwender
   * @return {@code true}, er wurde gelöscht
   * @throws SQLException
   *                      der Befehl konnte nicht ausgeführt werden
   */
  public boolean isUserLogicalDeleted(String userid) throws SQLException {
    String sql = persistence.getSQL(Persistence.KEY_IS_LDELETE_USER);
    logSQLKey(Persistence.KEY_IS_LDELETE_USER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, userid);
      try(ResultSet rs = statement.executeQuery()) {
        return rs.next() ? rs.getBoolean(LDELETE) : false;
      }
    }
  }



  /**
   * Nur für Testzwecke. Ist der User online?
   *
   *
   * @param user
   *             ein Testanwender
   * @return {@code true}, er ist online
   */
  public boolean isUserOnline(EntityUser user) {
    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_IS_ONLINE);
    logSQLKey(Persistence.KEY_IS_ONLINE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setString(1, user.getUid());
      try(ResultSet rs = prepstatement.executeQuery()) {
        done = rs.next();
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return done;
  }



  /**
   * Ist der Anwender online?
   *
   *
   * @param sessionid
   *                  die SessionID
   * @return {@code true}, der Anwender ist online
   */
  public boolean isUserOnline(String sessionid) {
    String sql = persistence.getSQL(Persistence.KEY_FETCH_USER_BY_SESSIONID);
    // Der SQL wird aus Gescwindigkeitsgründen bei der Sprachübertragung nicht
    // geloggt
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {

      prepStatement.setString(1, sessionid);
      try(ResultSet rs = prepStatement.executeQuery()) {
        return rs.next();
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return false;
  }



  /**
   * Hat der Anwender eine Übertragung am Laufen? Wenn der Anwender bereits Daten
   * empfängt oder selber Daten versendet, soll die Verbindung nicht aufgebaut
   * werden.
   * 
   * @param userid
   *               dieser Anwender
   * 
   * @return Anzahl der Bildschirmübertragungen
   * 
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   */
  public int isVideoRunning(String userid) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_IS_VIDEO_RUNNING);
    logSQLKey(Persistence.KEY_IS_VIDEO_RUNNING, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, userid);
      statement.setString(2, userid);
      try(ResultSet rs = statement.executeQuery()) {
        rs.next();
        return rs.getInt(1);
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }

  }



  /**
   * Ein User meldet sich an.
   * 
   * @param sessionID
   *                  die Verbindungskennung zwischen Webserver und Client
   * @param aes
   *                  dieser AES-Schlüssel
   * @param mail
   *                  diese Mailadresse
   * @param password
   *                  dieses Passwort
   * @param userid
   *                  dieser Benutzer
   * @param agent
   *                  mit diesem Gerät meldet sich der Benutzer an
   * @param onetime
   *                  dieses Einmal-Token
   * 
   * @return dieses Kommando wird an den Client zurückgesendet
   */
  public USRLOGIN loginUser(String sessionID, String aes, String mail, String password, String userid,
      Agent agent, String onetime) {

    USRLOGIN userlogin = new USRLOGIN();
    boolean wasDeleted = deleteToken(onetime);
    if (!wasDeleted) {
      userlogin.setCommand(Command.USRLOGIN);
      userlogin.setHeader(ERROR);
      userlogin.setDataset(Protocol.DATASET);
      userlogin.setText(new MultilingualString(KEY.LABEL_ANMELDUNG_FEHLGESCHLAGEN).toString());
      return userlogin;
    }

    if (userid == null) userid = "";
    if (mail == null) mail = "";

    String sqlInsert = persistence.getSQL(Persistence.INSERTUSER);
    String sql = persistence.getSQL(Persistence.USERLOGIN);
    String sqlTime = persistence.getSQL(Persistence.KEY_LAST_LOGIN);

    logSQLKey(Persistence.USERLOGIN, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepUSRLOGIN = connection.prepareStatement(sql);
      PreparedStatement prepstatement2 = connection.prepareStatement(sqlInsert);
      PreparedStatement prepstatement3 = connection.prepareStatement(sqlTime);
    ) {

      String hashedPassword = fetchPassword(userid, mail);
      // Das Password nach BCrypt wandeln, um vergleichen zu können
      boolean isPasswordCorrect = BCrypt.verifyer().verify(password.toCharArray(), hashedPassword).verified;
      if (!isPasswordCorrect) {
        userlogin.setCommand(Command.USRLOGIN);
        userlogin.setHeader(ERROR);
        userlogin.setDataset(Protocol.DATASET);
        userlogin.setText(new MultilingualString(KEY.LABEL_ANMELDUNG_FEHLGESCHLAGEN).toString());
        return userlogin;
      }

      prepUSRLOGIN.setString(1, userid);
      prepUSRLOGIN.setString(2, hashedPassword);
      prepUSRLOGIN.setString(3, mail);

      prepUSRLOGIN.setString(4, hashedPassword);
      try(ResultSet rs = prepUSRLOGIN.executeQuery()) {
        if (rs.next()) {

          if (isUserLogicalDeleted(rs.getString(UID))) {
            userlogin.setCommand(Command.USRLOGIN);
            userlogin.setHeader(ERROR);
            userlogin.setDataset(Protocol.DATASET);
            userlogin.setMultilingualkey(KEY.STRING_KONTO_DEAKTIVIERT);
            // Anwender ist logisch gelöscht
            // Dein Account ist vorübergehend gesperrt
            userlogin.setText(new MultilingualString(KEY.STRING_KONTO_DEAKTIVIERT).toString());
            return userlogin;
          }

          userlogin.setCommand(Command.USRLOGIN);
          userlogin.setHeader(CONFIRM);
          userlogin.setDataset(Protocol.DATASET);
          // Das Password war korrekt und muss als Nicht-MD5-Wert zurückgegeben werden.

          userlogin.setBackgroundColor(rs.getInt(BACKGROUND));
          userlogin.setForegroundColor(rs.getInt(FOREGROUND));
          userlogin.setVolume(rs.getInt("VOLUME"));
          userlogin.setOncall(rs.getBoolean("ONCALL"));
          userlogin.setNickname(rs.getString(NICKNAME));
          userlogin.setSession(sessionID);

          Token confirmIdentityToken = new Token();
          confirmIdentityToken.setEMail(rs.getString(MAIL));
          confirmIdentityToken.setPassword(password);
          confirmIdentityToken.setUserid(rs.getString(UID));

          SecretKey secretKey = Crypto.getAESFromBase64(aes);
          String encrypted = Crypto.encryptAES(confirmIdentityToken.toString(), secretKey);

          userlogin.setIdentity(encrypted);

          logSQLKey(Persistence.INSERTUSER, sql); // TB_ONLINE eintragen
          prepstatement2.setString(1, confirmIdentityToken.getUserid());
          prepstatement2.setString(2, sessionID);
          prepstatement2.setInt(3, agent.idValue());
          prepstatement2.setString(4, aes);
          prepstatement2.executeUpdate();
          logSQLKey(Persistence.KEY_LAST_LOGIN, sql);
          prepstatement3.setString(1, userid);
          prepstatement3.executeUpdate();
        }
        else {
          // Fehler
          userlogin.setCommand(Command.USRLOGIN);
          userlogin.setHeader(ERROR);
          userlogin.setDataset(Protocol.DATASET);
          userlogin.setText(new MultilingualString(KEY.LABEL_ANMELDUNG_FEHLGESCHLAGEN).toString());
        }

      }
      return userlogin;
    }
    catch (SQLException e) {
      log.fatal(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }
  }



  protected void logSQLKey(String key, String sql) {
    if (log.isDebugEnabled()) {
      log.debug("KEY:    " + key);
      log.debug("Command:" + sql);
      log.debug("--");
    }
  }



  /**
   *
   * Finde alle Konferenzen, an denen {@code uid} teilnimmt.
   *
   * @param uid
   *            ein Anwender
   *
   * @return alle Konferenzen, an denen der Anwender teilnimmt
   */
  public List<TransferKonferenzraum> meineKonferenzen(String uid) {
    ArrayList<TransferKonferenzraum> meineKonferenzen = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_MEINE_KONFERENZEN);
    logSQLKey(Persistence.KEY_MEINE_KONFERENZEN, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement preparedStatement = connection.prepareStatement(sql)
    ) {

      preparedStatement.setString(1, uid);
      try(ResultSet rs = preparedStatement.executeQuery()) {
        while (rs.next()) {
          TransferKonferenzraum reuniones = new TransferKonferenzraum();
          reuniones.setKonferenzraum(rs.getString("KONFERENZRAUM"));
          reuniones.setOrganisator(rs.getString("NICKNAME"));
          reuniones.setBeschreibung(rs.getString("TEXTO"));
          reuniones.setUserid(rs.getString("UID"));
          meineKonferenzen.add(reuniones);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return meineKonferenzen;
  }



  /**
   * Ein Anwender wird stummgeschaltet. Nur für Testzwecke.
   * 
   * @param konferenzraum
   *                      dieser Konfernzraum
   * @param organisator
   *                      hat die Konferenz eingerichtet
   * @param userid
   *                      dieser Anwender hat sein Mikrofon abgestellt
   * 
   * @throws SQLException
   *                      der SQL-Befehl konnte nicht ausgeführt werden
   */
  public void mute(String konferenzraum, String organisator, String userid) throws SQLException {
    String sql = persistence.getSQL(Persistence.KEY_TEST_MUTE);
    logSQLKey(Persistence.KEY_TEST_MUTE, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setString(1, konferenzraum);
      prepstatement.setString(2, organisator);
      prepstatement.setString(3, userid);
      prepstatement.executeUpdate();

    }

  }



  /**
   * Eine Liste neuer Mitgliedsanträge wird erstellt. Diese Liste soll an den
   * Administrator verschickt werden. Der Administrator prüft die Anträge. Die
   * Anträge haben den Benutzerstatus=2 'möchte Vollmitglied werden'.
   *
   *
   * @return die zurückgegebene Liste kann leer sein
   */
  public synchronized List<EntityUser> readBenutzerAntraege() {
    ArrayList<EntityUser> user = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_INFORM_ADMIN);
    logSQLKey(Persistence.KEY_INFORM_ADMIN, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql)
    ) {

      while (rs.next()) {
        EntityUser tmp = new EntityUser();
        tmp.setUid(rs.getString("UID"));
        tmp.setMail(rs.getString("MAIL"));
        tmp.setNickname(rs.getString("NICKNAME"));
        tmp.setPassword(rs.getString("PASSWORD"));
        Timestamp timestamp = rs.getTimestamp(TIME);
        tmp.setTime(ZonedDateTime.of(timestamp.toLocalDateTime(), ZoneId.of(TIMEZONE)));
        tmp.setBenutzerstatus(rs.getInt("BENUTZERSTATUS"));
        user.add(tmp);
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return user;
  }



  /**
   * Lies alle Konferenzteilnehmer aus.
   *
   *
   * @param konferenzname
   *                      die Konferenz
   * @param organisator
   *                      der Organisator
   *
   * @return die zurückgegebene Liste enthält n-Paare (Nicknames/Userid)
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   */
  public TreeMap<String, String> readKonferenzteilnehmer(String konferenzname, String organisator)
      throws DatabaseException {
    TreeMap<String, String> map = new TreeMap<>();
    String sql = persistence.getSQL(Persistence.KEY_READ_TEILNEHMER);
    logSQLKey(Persistence.KEY_READ_TEILNEHMER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setString(1, konferenzname);
      prepstatement.setString(2, organisator);
      try(ResultSet rs = prepstatement.executeQuery()) {
        while (rs.next()) {
          map.put(rs.getString(NICKNAME), rs.getString("TB_USER.UID"));
        }
      }

    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
    return map;
  }



  /**
   * Alle Konferenzteilnehmer werden mit ihren Attributen ausgelesen.
   *
   * @param konferenzname
   *                      die Koneferenz
   * @param organisator
   *                      der Organisator
   *
   * @return der Protokollbefehl {@code CONFERENCE}
   */
  public CONFERENCE readKonferenzuser(String konferenzname, String organisator) {

    ArrayList<KonferenzraumUser> list = new ArrayList<>();
    CONFERENCE conference = new CONFERENCE();
    conference.setCommand(Command.CONFERENCE);
    conference.setHeader(RESPONSE);
    conference.setDataset(Protocol.DATASET);
    conference.setKonferenzname(konferenzname);
    conference.setOrganisatorUid(organisator);
    String sql = persistence.getSQL(Persistence.KEY_KONFERENZRAUM_STATUS);
    logSQLKey(Persistence.KEY_KONFERENZRAUM_STATUS, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setString(1, konferenzname);
      prepstatement.setString(2, organisator);

      try(ResultSet rs = prepstatement.executeQuery()) {
        while (rs.next()) {
          KonferenzraumUser user = new KonferenzraumUser();
          user.setBackgroundColor(rs.getInt(BACKGROUND));
          user.setForegroundColor(rs.getInt(FOREGROUND));
          user.setNickname(rs.getString(NICKNAME));
          user.setAnwesend(rs.getBoolean("OFFHOOK"));
          user.setMute(rs.getBoolean("MUTE"));
          user.setUserid(rs.getString("result.UID")); // userid
          user.setSession(rs.getString(SESSION));
          list.add(user);
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    conference.setKonferenzraumUser(list.toArray(new KonferenzraumUser[list.size()]));
    return conference;
  }



  /**
   * Lies alle Telefonkonferenzen von {@code userid}.
   *
   * @param userid
   *               ein Anwender
   * @return eine Liste von Telefonkonferenzen
   * @throws DatabaseException
   */
  public List<Telko> readTelefonkonferenzen(String userid) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_READ_TELEFONKONFERENZEN);
    logSQLKey(Persistence.KEY_READ_TELEFONKONFERENZEN, sql);
    ArrayList<Telko> telkos = new ArrayList<>();
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setString(1, userid);
      try(ResultSet rs = prepstatement.executeQuery()) {
        while (rs.next()) {
          Telko telko = new Telko();
          telkos.add(telko);
          telko.setKonferenzraum(rs.getString("KONFERENZRAUM"));
          telko.setTexto(rs.getString("TEXTO"));
          telko.setOrganisator(rs.getString("ORGANISATOR"));
          // in der Datenbank ist eine TIMEZONE enthalten
          telko.setUntil(ZonedDateTime.of(rs.getTimestamp("UNTIL").toLocalDateTime(), ZoneId.of(TIMEZONE)));
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
    return telkos;
  }



  /**
   * Lösche alle Nicknames, die nicht in der übergebenen Liste enthalten sind.
   *
   *
   * @param remain
   *               eine Positivliste, die erhalten bleibt
   * @return {@code true}, die Operation wurde durchgeführt
   */
  public boolean remainNicknames(List<String> remain) {
    boolean done = false;
    StringBuilder sql = new StringBuilder();
    sql.append("delete from TB_NICKNAMES where NICKNAME NOT IN ").append("(");
    for (String nickname : remain) {
      sql.append("'");
      sql.append(nickname);
      sql.append("'").append(",");
    }
    sql.setLength(sql.length() - 1);
    sql.append(")");
    logSQLKey("REMAIN_NICKNAMES", sql.toString());
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
    ) {
      statement.executeUpdate(sql.toString());
      done = true;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return done;
  }



  /**
   * Lösche alle Programmversionen, die nicht in der übergebenen Liste enthalten
   * sind.
   *
   *
   * @param remain
   *               eine Positivliste, die erhalten bleibt
   * @return {@code true}, die Operation wurde durchgeführt
   */
  public boolean remainOutdated(List<String> remain) {
    boolean done = false;
    StringBuilder sql = new StringBuilder();
    if (remain.size() == 0) {
      // Sonderfall
      sql.append("delete from TB_OUTDATED");
    }
    else {
      sql.append("delete from TB_OUTDATED where PROGRAMMVERSION NOT IN ").append("(");
      for (String outdated : remain) {
        sql.append("'");
        sql.append(outdated);
        sql.append("'").append(",");
      }
      sql.setLength(sql.length() - 1);
      sql.append(")");
    }
    logSQLKey("REMAIN_OUTDATED", sql.toString());
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
    ) {
      statement.executeUpdate(sql.toString());
      done = true;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return done;
  }



  /**
   * Ein Anwender verlässt einen Chatraum. Der Anwender wird gelöscht.
   *
   * @param userid
   *               dieser Anwender verlässt den Raum
   * @param room
   *               der Chatraum
   * @return
   */
  public void remove(String userid, String room) {
    String sql = persistence.getSQL(Persistence.KEY_LEAVEROOM);
    logSQLKey(Persistence.KEY_LEAVEROOM, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setString(1, userid);
      prepstatement.setString(2, room);
      prepstatement.executeUpdate();

    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Der Nickname in TB_PRIVATE_CHATFILE wird ersetzt, weil der Anwender seinen
   * Nickname geändert hat.
   * 
   * @param neu
   *            der neue Nickname
   * @param alt
   *            der alte Nickname
   * 
   * @return Anzahl der Änderungen [0..n], -1 bei Fehler
   * 
   */
  public int replacePrivateRoomname(String neu, String alt) {
    String sql = persistence.getSQL(Persistence.KEY_REPLACE_NICKNAME);
    logSQLKey(Persistence.KEY_REPLACE_NICKNAME, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      prepStatement.setString(1, neu);
      prepStatement.setString(2, alt);
      return prepStatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Alle verbotenen Nicknames werden auf einen Zufallswert zurückgesetzt.
   *
   * @param verboteneNamen
   *                       diese Spitznamen sind nicht erlaubt
   */
  public void resetNicknames(List<EntityUser> verboteneNamen) {
    // TODO der Nickname muss in allen Tabellen neu gesetzt werden.
    // TB_CHAT,TB_USER,

    verboteneNamen.forEach(entityUser -> {
      String neuerNickname = createNickname(Constants.LEN_NICKNAME);
      entityUser.setNickname(neuerNickname);
      updateNickname(entityUser);
    });
  }



  /**
   * Entziehe dem Anwender die Zugriffsberechtigung für alle Module. Die Methode
   * ist für Testzwecke gedacht.
   *
   *
   * @param userid
   *               der Anwender
   *
   * @return alle Module mit Zugriffsberechtigung
   *
   * @throws SQLException
   *                      der SQL-Befehl konnte nicht ausgeführt werden
   */
  public List<UserModule> revokeAllModules(String userid) throws SQLException {
    ArrayList<UserModule> list = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_REVOKE_ALL_MODULES);
    logSQLKey(Persistence.KEY_REVOKE_ALL_MODULES, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      prepStatement.setString(1, userid);
      prepStatement.executeUpdate();
    }
    return list;
  }



  /**
   * Entziehe dem Anwender eine Modulberechtigung.
   *
   *
   * @param userid
   *               der Anwender
   * @param module
   *               ein Modul
   */
  public void revokeModule(String userid, int module) {
    String sql = persistence.getSQL(Persistence.KEY_REVOKE_MODULE);
    logSQLKey(Persistence.KEY_REVOKE_MODULE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      prepStatement.setString(1, userid);
      prepStatement.setInt(2, module);
      prepStatement.executeUpdate();
    }
    catch (SQLException e) {
      log.warn(e.getMessage(), e.getCause());
    }
  }



  /**
   * Die Dateiattribute einer im Chat angehefteten Datei werden gespeichert.
   * 
   * @param filesize
   *                         diese Dateigröße
   * @param chatid
   *                         dieser Chatraum
   * @param absoluteFilename
   *                         diese Datei mit Pfadangabe
   * @param filename
   *                         der Dateiname ohne Pfadangabe
   * 
   * @return dieser Datenbankschlüssel verweist auf die Attribute
   * 
   * @throws DatabaseException
   *                           die Dateiattribute verursachten einen
   *                           Verarbeitungsabbruch
   */
  public Long saveChatfile(Long filesize, String chatid, String absoluteFilename, String filename)
      throws DatabaseException {
    long anlage = createIdentifier();
    String sql = persistence.getSQL(Persistence.KEY_INSERT_CHATFILES);
    logSQLKey(Persistence.KEY_INSERT_CHATFILES, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      prepStatement.setLong(1, anlage);
      prepStatement.setLong(2, filesize);
      prepStatement.setString(3, chatid);
      prepStatement.setString(4, absoluteFilename);
      prepStatement.setString(5, filename);
      prepStatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
    return anlage;
  }



  public DOWNLOAD selectDOWNLOAD(DOWNLOAD request) {
    DOWNLOAD response = new DOWNLOAD();
    String sql = persistence.getSQL(Persistence.KEY_FETCH_FILE_BY_NUMBER);
    logSQLKey(Persistence.KEY_FETCH_FILE_BY_NUMBER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setInt(1, request.getIndex());
      try(ResultSet rs = statement.executeQuery();) {
        if (rs.next()) {
          // Antwort zusammenbauen
          response.setCommand(Command.DOWNLOAD);
          response.setHeader(RESPONSE);
          response.setDataset(Protocol.DATASET);
          response.setPathfile(rs.getString("FILE"));
          response.setIp(rs.getString("IP"));
          response.setPort(rs.getInt("PORT"));
          response.setIndex(request.getIndex());
          response.setSlot(request.getSlot());
          response.setUserid(request.getUserid());
          response.setFilename(request.getFilename());

        }
        else {
          // error vielleicht zwischenzeitlich entfernt
          response.setCommand(Command.DOWNLOAD);
          response.setHeader(ERROR);
          response.setDataset(Protocol.DATASET);
          response.setIndex(request.getIndex());
          response.setSlot(request.getSlot());
          response.setUserid(request.getUserid());
          response.setFilename(request.getFilename());
          StringBuilder buffer = new StringBuilder();
          buffer.append("The requested file '");
          buffer.append(request.getFilename());
          buffer.append("' was removed.");
          response.setText(buffer.toString());
        }
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return response;
  }



  /**
   * binary filename rlike 'ausdruck' or binary filename rlike 'ausdruck2' or ...
   *
   * Aktuell werden maximal 64 zurückgegeben.
   *
   * @param patterns
   *                 ist ein Suchstring
   * @param maxFiles
   *                 maximale Anzahl zu übertragener Dateien
   *
   * @return gefundene Datei
   */
  public List<Searchfile> selectSearchfiles(String[] patterns, int maxFiles) {
    if (patterns.length == 0) return new ArrayList<>();
    // SQL zusammenbauen

    // select filename from t_transfer_lib where binary filename rlike
    // 'ausdruck' or binary filename rlike 'ausdruck2'

    // binary filename rlike 'ausdruck'
    // or
    // binary filename rlike 'ausdruck2'

    String sql = persistence.getSQL(Persistence.KEY_SEARCH_FILES);
    // ausdruck'
    StringBuilder dbCommand = new StringBuilder();
    dbCommand.append(sql);
    dbCommand.append(" filename regexp '");
    dbCommand.append(patterns[0]);
    dbCommand.append("'");
    for (int index = 1; index < patterns.length; index++) {
      dbCommand.append(" or filename regexp '");
      dbCommand.append(patterns[index]);
      dbCommand.append("'");
    }

    ArrayList<Searchfile> found = new ArrayList<>();
    logSQLKey(Persistence.KEY_SEARCH_FILES, dbCommand.toString());
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(dbCommand.toString());
    ) {

      int index = 0;
      while (rs.next()) {
        if (index >= maxFiles) {
          log.info("es gibt weitere Blöcke");
          break;
        }
        Searchfile file = new Searchfile();
        file.setFilename(rs.getString("FILENAME"));
        file.setIndex(rs.getInt("NUMBER"));
        file.setFilesize(rs.getLong(FILESIZE));
        found.add(file);
        index++;
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return found;
  }



  /**
   * Welcher Anwender sendet an den Empfänger?
   * 
   * @param receiverUserid
   *                       dieser Empfänger
   * @return dieser Anwender sendet an den Empfänger, {@code oder null}
   * 
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht verarbeitet werden
   */
  public String senderFromReceiver(String receiverUserid) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_SENDER_FROM_RECEIVER);
    logSQLKey(Persistence.KEY_SENDER_FROM_RECEIVER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepSender = connection.prepareStatement(sql);
    ) {
      prepSender.setString(1, receiverUserid);
      try(ResultSet rs = prepSender.executeQuery()) {
        if (rs.next()) {
          return rs.getString(SENDER);
        }
        log.error(receiverUserid + " - dieser Empfänger hat keinen zugehörigen Sender");
        throw new DatabaseException(receiverUserid + " - dieser Empfänger hat keinen zugehörigen Sender");
      }
    }
    catch (SQLException e) {
      log.fatal(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
  }



  /**
   * Der Anwender hat den Hörer abgenommen oder ist am telefonieren.
   *
   *
   * @param userid
   *               Userid
   * @param isBusy
   *               {@code true}, der Anwender telefoniert
   */
  public void setOnBusy(String userid, boolean isBusy) {
    String sql = persistence.getSQL(Persistence.KEY_ONBUSY);
    logSQLKey(Persistence.KEY_ONBUSY, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setBoolean(1, isBusy);
      statement.setString(2, userid);
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Eine Projektorverbindung wird aufgebaut und als aktive Sitzung eingetragen.
   *
   *
   * @param receiverSesseion
   *                         der Empfänger
   * @param callerSession
   *                         der Sender
   */
  public void setProjektorSession(String receiverSesseion, String callerSession) {
    String sql = persistence.getSQL(Persistence.KEY_INSERT_ON_PROJEKTOR);
    logSQLKey(Persistence.KEY_INSERT_ON_PROJEKTOR, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {

      prepStatement.setString(1, receiverSesseion);
      prepStatement.setString(2, callerSession);
      prepStatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
  }



  /**
   * DatabaseThread beenden.
   */
  public abstract void shutdown();



  /**
   * Ein neuer Anwender wird in die Datenbank aufgenommen.<br>
   * <br>
   * 1. Die eMail Adresse muss gültig sein<br>
   * 2. Ist die eMail bereits vorhanden?<br>
   *
   * 3.1 Die eMail ist vorhanden<br>
   * 3.2 ein neues PASSWORD wird erzeugt<br>
   *
   * 4.1 die eMail ist nicht vorhanden<br>
   * 4.2 ein PASSWORD wird erezugt 4.3 eine UID wird erzeugt<br>
   *
   * 5. eine eMail Benachrichtigung wird gesendet
   *
   *
   * @param mail
   *                   die Email-Adresse vom Anwender
   * @param webconfirm
   *                   {@code true} ist von der Homepage, {@code false} ist vom
   *                   Desktop
   *
   * @return {@code true} die Anfrage kommt von der Homepage
   * 
   */
  public SIGNIN signinUser(String mail, boolean webconfirm) {
    SIGNIN signin = new SIGNIN();
    Matcher matcher = Util.PATTERN_EMAIL.matcher(mail);
    if (!matcher.matches()) {
      signin.setHeader(ERROR);
      signin.setCommand(Command.SIGNIN);
      signin.setDataset(Protocol.DATASET);
      signin.setErrorCode(Benutzerstatus.MITGLIEDSCHAFT_ABGELEHNT.getStatus());
      signin.setEmail(mail);
      signin.setMultilingualkey(KEY.SERVER_EMAIL_INVALID);
      return signin;
    }
    String sql = persistence.getSQL(Persistence.KEY_CREATE_USER);
    String sqlFTS = persistence.getSQL(Persistence.INSERT_FTS_CONFIG);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement2 = connection.prepareStatement(sql);
      PreparedStatement prepstatement3 = connection.prepareStatement(sqlFTS);
    ) {

      EntityUser user = fetchUserByMail(mail);

      if (user != null) {
        // Anwender ist registriert
        if (isUserLogicalDeleted(user.getUid())) {
          signin.setHeader(ERROR);
          signin.setCommand(Command.SIGNIN);
          signin.setDataset(Protocol.DATASET);
          signin.setEmail(mail);
          signin.setErrorCode(Benutzerstatus.VOLLMITGLIED.getStatus());
          signin.setErrorMessage("Dein Konto ist vorübergehend gesperrt."); // kein Datenbankfehler
          signin.setMultilingualkey(KEY.STRING_KONTO_DEAKTIVIERT);
          return signin;
        }
        // 3.1
        // email gefunden
        // update auf password
        // der rest bleibt unverändert

        // Benutzerkonto deaktiviert?
        // Der Meldetext muss lauten
        // Dein Konto wurde deaktiviert. Ein Administrator muss die Sperre aufheben.

        Benutzerstatus status = Benutzerstatus.toBenutzerstatus(user.getBenutzerstatus());
        boolean confirmation_flag = user.getConfirmation_flag() == 1;

        switch(status) {
          case VOLLMITGLIED:

            // neu über das Web Passwort anfordern
            // über Desktop bist bereits Vollmitglied

            if (webconfirm) {
              if (confirmation_flag) {
                // kein Update vornehmen Hacker oder noch nicht angemeldet
                signin.setHeader(ERROR);
                signin.setCommand(Command.SIGNIN);
                signin.setDataset(Protocol.DATASET);
                signin.setEmail(mail);
                signin.setErrorCode(Benutzerstatus.VOLLMITGLIED.getStatus());
                signin.setErrorMessage("Dein Konto ist vorübergehend gesperrt."); // kein Datenbankfehler
                MultilingualString denied = new MultilingualString(
                    KEY.STRING_KONTO_GESPERRT, ISO639.fromValue(user.getLanguage().toString())
                );
                signin.setMultilingualkey(KEY.STRING_KONTO_GESPERRT);
                signin.setText(denied.toString());
              }
              else {
                // update vornehmen von password // Neues Password anfordern
                signin = createConfirmationKey(user.getUid());
                signin.setNickname(user.getNickname());
                signin.setEmail(mail);
                signin.setForegroundColor(user.getForeground());
                signin.setBackgroundColor(user.getBackground());
              }
            }
            else {

              // senden als ERROR, ist bereits eingeschrieben
              // Der Anwender ist VOLLMITGLIED registriert und macht erneut ein SIGNIN
              MultilingualString subscribe = new MultilingualString(
                  KEY.SERVER_VOLLMITGLIED_EINSCHREIBEN, ISO639.fromValue(user.getLanguage().toString())
              );
              signin.setCommand(Command.SIGNIN);
              signin.setHeader(ERROR);
              signin.setDataset(Protocol.DATASET);
              signin.setMultilingualkey(KEY.SERVER_VOLLMITGLIED_EINSCHREIBEN);
              signin.setErrorCode(Benutzerstatus.VOLLMITGLIED.getStatus());
              signin.setErrorMessage("Ein Vollmitglied hat sich erneut als Benutzer eingeschrieben.");
              signin.setText(subscribe.toString());
            }

            // Was hier kommt ist PASSWORD anfordern

            return signin;

          case MÖCHTE_VOLLMITGLIED_WERDEN:
            signin.setHeader(ERROR);
            signin.setCommand(Command.SIGNIN);
            signin.setDataset(Protocol.DATASET);
            signin.setEmail(mail);
            // ErrorMeddage ist Pflicht
            signin.setErrorMessage("möchte Mitglied werden");
            signin.setErrorCode(Benutzerstatus.MÖCHTE_VOLLMITGLIED_WERDEN.getStatus());
            // Text ist optional, wird aber in der Browserversion ausgegeben
            signin.setMultilingualkey(KEY.STRING_MITGLIEDSCHAFT_WIRD_BAERABEITET);
            return signin;
          case VOLLMITGLIEDSCHAFT_BESTÄTIGT:
            signin.setHeader(ERROR);
            signin.setCommand(Command.SIGNIN);
            signin.setDataset(Protocol.DATASET);
            signin.setEmail(mail);
            signin.setErrorMessage("bestätigt");
            signin.setErrorCode(Benutzerstatus.VOLLMITGLIEDSCHAFT_BESTÄTIGT.getStatus());
            signin.setMultilingualkey(KEY.STRING_MITGLIEDSCHAFT_BESTAETIGT);
            return signin;
          case MITGLIEDSCHAFT_ABGELEHNT:
            signin.setHeader(ERROR);
            signin.setCommand(Command.SIGNIN);
            signin.setDataset(Protocol.DATASET);
            signin.setEmail(mail);
            signin.setErrorMessage("abgelehnt");
            signin.setErrorCode(Benutzerstatus.MITGLIEDSCHAFT_ABGELEHNT.getStatus());
            signin.setMultilingualkey(KEY.STRING_MITGLIEDSCHAFT_WURDE_ABGELEHNT);
            return signin;
        }

      }
      String password = Util.getRandomID();
      String uid = Util.getRandomID();

      // Der Anwender ist nicht registriert und wird angelegt
      String[] result = mail.split("@");
      String nickname = result[0];

      logSQLKey(Persistence.KEY_CREATE_USER, sql);
      prepstatement2.setString(1, uid);
      prepstatement2.setString(2, mail);
      prepstatement2.setString(3, nickname);
      prepstatement2.setString(4, BCrypt.withDefaults().hashToString(12, password.toCharArray())); // MD5
      prepstatement2.setBoolean(5, false);
      prepstatement2.executeUpdate();

      // Der Benutzer wurde angelegt
      // Welchen Status bekommt der Nutzer
      // Status=1 der Benutzer ist bestätigt
      // Status=2 der Benutzer möchte Vollmitglied werden
      // Wenn die Domäne öffentlich ist, Status 2=möchte Mitglied werden
      // Wenn die Domäne privat ist, Status 1=Vollmitglied

      // FTS_CONFIG anlegen

      logSQLKey(Persistence.INSERT_FTS_CONFIG, sqlFTS);
      prepstatement3.setString(1, uid);
      prepstatement3.executeUpdate();

      // Domänenstatus

      Config config = fetchConfig();
      updateBenutzerstatus(
          config.getIsPublic() ? Benutzerstatus.VOLLMITGLIED : Benutzerstatus.MÖCHTE_VOLLMITGLIED_WERDEN, uid
      );
      if (!config.getIsPublic()) {
        signin.setHeader(ERROR);
        signin.setCommand(Command.SIGNIN);
        signin.setDataset(Protocol.DATASET);
        signin.setEmail(mail);
        signin.setErrorMessage("möchte Mitglied werden");
        signin.setErrorCode(Benutzerstatus.MÖCHTE_VOLLMITGLIED_WERDEN.getStatus());
        signin.setMultilingualkey(KEY.STRING_MITGLIEDSCHAFT_WIRD_BAERABEITET);
        return signin;
      }

      signin.setHeader(CONFIRM);
      signin.setCommand(Command.SIGNIN);
      signin.setDataset(Protocol.DATASET);
      signin.setEmail(mail);
      signin.setNickname(nickname);
      signin.setPassword(password);
      signin.setUserid(uid);
      signin.setBackgroundColor(-1);
      signin.setForegroundColor(-16777216);
      signin.setMultilingualkey(webconfirm ? KEY.SERVER_WEBCONFIRM_PASSWORT : KEY.SERVER_CONFIRM_PASSWORT);
    }
    catch (SQLException e) {
      log.fatal(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
    return signin;
  }



  /**
   * Alle Projektoren werden gelöscht. Zu diesem Zeitpunkt kann es keine
   * eingeschalteten Projektoren geben.
   */
  public boolean switchOffProjectors() {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_ALL_PROJEKTORS);
    logSQLKey(Persistence.KEY_DELETE_ALL_PROJEKTORS, sql);

    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
    ) {
      statement.executeUpdate(sql);
      return true;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }

  }



  /**
   * Der Projektor wird eingeschaltet oder ausgeschaltet.
   * 
   * @param onProjektor
   *                    {@code true}, Projektor einschalten
   * @param userid
   *                    dieser Anwender
   * @return {@code true}, der Projektor ist eingeschaltet
   */
  public boolean switchProjektor(boolean onProjektor, String userid) {
    EntityUser user = new EntityUser();
    user.setUid(userid);
    user.setOnprojektor(onProjektor);
    return updateOnprojektor(user);
  }



  /**
   * Lies alle zu löschenden Nicknames aus.
   *
   * @return die verbotenen Namen
   */
  public List<EntityUser> toDeleteNicknames() {
    ArrayList<EntityUser> entityUserlist = new ArrayList<>();
    String sql = persistence.getSQL(Persistence.KEY_FETCH_DELETE_NICKNAMES);
    logSQLKey(Persistence.KEY_FETCH_DELETE_NICKNAMES, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
      ResultSet rs = statement.executeQuery(sql)
    ) {
      while (rs.next()) {
        EntityUser entity = new EntityUser();
        entity.setUid(rs.getString("UID"));
        entity.setNickname(rs.getString("NICKNAME"));
        entityUserlist.add(entity);
      }
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return entityUserlist;
  }



  /**
   * Wenn der Anwender das Programm verläßt, wird das Mikrofon auf nicht stumm
   * gestellt.
   * 
   * @param userid
   *               dieser Anwender
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   * 
   */
  public void unmute(String userid) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_UNMUTE);
    logSQLKey(Persistence.KEY_UPDATE_UNMUTE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setString(1, userid);
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }

  }



  /**
   * Der Benutzer bekommt einen Status zugewiesen.
   *
   *
   * @param status
   *               sein neuer Status
   * @param userid
   *               die userid
   *
   * @return {@code true}, der Benutzerstatus wurde gespeichert
   */
  public boolean updateBenutzerstatus(Benutzerstatus status, String userid) {
    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_BENUTZERSTATUS);
    logSQLKey(Persistence.KEY_UPDATE_BENUTZERSTATUS, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {

      statement.setInt(1, status.getStatus());
      statement.setString(2, userid);
      statement.executeUpdate();
      done = true;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return done;
  }



  /**
   * Der Anwender stellt ein, ob er Chatanfragen annehmen möchte oder nicht.
   *
   * @param userid
   *                   eine Benutzer-ID
   * @param chatdenied
   *                   {@code true}, Chatanfragen werden generell abgelehnt
   */
  public void updateChatdenied(String userid, boolean chatdenied) {
    String sql = persistence.getSQL(Persistence.KEY_CHATDENIED);
    logSQLKey(Persistence.KEY_CHATDENIED, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setBoolean(1, chatdenied);
      statement.setString(2, userid);
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }

  }



  /**
   * Hochgeladene CHATFILES können wieder gelöscht werden. Dieser SQL wird nur
   * nach einem Serverneustart aufgerufen.
   *
   */
  public void updateChatfiles() {
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_CHATFILES);
    logSQLKey(Persistence.KEY_UPDATE_CHATFILES, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
    ) {
      statement.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Chattooltip wird gesetzt.
   *
   * @param chattooltip
   *                    {@code true}, in der Oberfläche wird der Tooltip angezeigt
   */
  public void updateChattooltip(boolean chattooltip, String userid) {
    String sql = persistence.getSQL(Persistence.KEY_WRITE_CHATTOOLTIP);
    logSQLKey(Persistence.KEY_WRITE_CHATTOOLTIP, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setBoolean(1, chattooltip);
      statement.setString(2, userid);
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Diese Methode ist für Testzwecke gedacht.
   *
   * @param config
   *               die Konfigurationseinstellungen
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   */
  public void updateConfig(Config config) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_CONFIG);
    logSQLKey(Persistence.KEY_UPDATE_CONFIG, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {

      statement.setString(1, config.getDomain());
      statement.setBoolean(2, config.getIsPublic());
      statement.setInt(3, config.getFlipToForum());
      statement.setInt(4, config.getHours());
      statement.setInt(5, config.getAccount());
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }

  }



  /**
   * Setze das Confirmation-Flag. Das Flag wird auf {@code 0} bei jeder Anmeldung
   * zurückgesetzt.
   *
   * @param uid
   *              die UserID
   * @param email
   *              die Email-Adresse
   * @param flag
   *              {@code 1} bei einer Passwordanforderung, sonst 0
   */
  public void updateConfirmationFlag(String uid, String email, int flag) {
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_CONFIRMATION_FLAG);
    logSQLKey(Persistence.KEY_UPDATE_CONFIRMATION_FLAG, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setInt(1, flag);
      statement.setString(2, uid);
      statement.setString(3, email);
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Ein SIGNIN erfolgt, nach dem ein User bereits vorhanden ist. Der User bekommt
   * ein neues PASSWORD.
   *
   * @param password
   *                 das neue PASSWORD
   * @param uid
   *                 die UserID
   * @param mail
   *                 die eMail Adresse
   */
  public void updateConfirmPassword(String password, String uid, String mail) {
    if (uid == null && mail == null) {
      log.error("UID and MAIL are null");
      return;
    }
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_CONFIRM_PASSWORD);
    logSQLKey(Persistence.KEY_UPDATE_CONFIRM_PASSWORD, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, password);
      statement.setString(2, uid);
      statement.setString(3, mail);
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Darf die hochgeladene Datei gelöscht werden?
   *
   *
   * @param deletable
   *                  {@code true}, die Datei darf gelöscht werden
   * @param anlage
   *                  eine Referenz auf die Datei
   */
  public void updateDeletable(boolean deletable, Long anlage) {
    if (anlage == null) return;
    String sql = persistence.getSQL(Persistence.KEY_DELETABLE);
    logSQLKey(Persistence.KEY_DELETABLE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      prepStatement.setBoolean(1, deletable);
      prepStatement.setLong(2, anlage);
      prepStatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Die Oberflächeneinstellung wird in der Datenbank gespeichert.
   *
   *
   * @param userid
   *               eine Benutzer-ID
   * @param eis
   *               eine Eissorte
   */
  public void updateEis(String userid, int eis) {
    String sql = persistence.getSQL(Persistence.KEY_WRITE_EIS);
    logSQLKey(Persistence.KEY_WRITE_EIS, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setInt(1, eis);
      prepstatement.setString(2, userid);
      prepstatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
  }



  /**
   * Aktualisiere die Benutzereinstellungen für den FiletransferService.
   *
   * @param config
   *               die neuen Benutzereintsellungen
   * @return {@code true}, die Benutzereinstellungen wurden gespeichert
   */
  public boolean updateFiletransferConfig(FiletransferConfig config) {
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_FTS_CONFIG);
    logSQLKey(Persistence.KEY_UPDATE_FTS_CONFIG, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setInt(1, config.getPort());
      prepstatement.setInt(2, config.getStarttype());
      prepstatement.setString(3, config.getDownloadDir());
      prepstatement.setString(4, config.getUploadDir());
      prepstatement.setString(5, config.getUid());
      return prepstatement.executeUpdate() > 0;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return false;
  }



  /**
   * Speichere das Download- und Upload- Verzeichnis ab.
   *
   * @param config
   *               die Benutzereinstellungen
   * @return {@code true}, die Verzeichnisse wurden gespeichert
   */
  public boolean updateFiletransferDirectories(FiletransferConfig config) {
    String sql = persistence.getSQL(Persistence.WRITE_FTS_DIRECTORIES);
    logSQLKey(Persistence.WRITE_FTS_DIRECTORIES, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setString(1, config.getDownloadDir());
      prepstatement.setString(2, config.getUploadDir());
      prepstatement.setString(3, config.getUid());
      return prepstatement.executeUpdate() > 0;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return false;
  }



  /**
   * Lösche übertragbare Dateien, wenn der Anwender sie in der Zwischenzeit als
   * gesperrt markiert hat. Die Dateiendng bestimmt, welche Dateien aus der
   * Austauschtabelle gelöscht werden.
   *
   *
   * @param userid
   *               die Userid
   * @param suffix
   *               eine Dateiendung
   */
  void updateFiletransferList(String userid, String suffix) {
    String sql = persistence.getSQL(Persistence.KEY_DELETE_USER_SUFFIXES);
    logSQLKey(Persistence.KEY_DELETE_USER_SUFFIXES, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {

      statement.setString(1, userid);
      statement.setString(2, suffix);
      statement.setString(3, "%" + suffix);
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Alle verbotenen Nicknames werden übergeben und gespeichert.
   *
   * @param list
   *             verbotene Nicknames
   *
   * @return {@code true}, wurde ausgeführt
   */
  public boolean updateForbiddenNicknames(List<String> list) {
    boolean done = false;
    StringBuilder sql = new StringBuilder();
    sql.append("insert ignore into TB_NICKNAMES (NICKNAME) values ");
    for (String nickname : list) {
      sql.append("('");
      sql.append(nickname);
      sql.append("'),");
    }
    sql.setLength(sql.length() - 1);
    logSQLKey("INSERT_NICKNAME", sql.toString());
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
    ) {
      statement.executeUpdate(sql.toString());
      done = true;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return done;
  }



  /**
   * Globale Benutzerattribute werden gespeichert.
   *
   *
   * @param userid
   *                     die Benutzer-Id
   * @param playervolume
   *                     die Playerlautstärke
   * @param iptvvolume
   *                     die Fernseherlautstärke
   * @param eis
   *                     die Oberflächenfarbe
   * @param language
   *                     die Oberflächensprache
   */
  public void updateGlobalAttributes(String userid, int playervolume, int iptvvolume, int eis,
      String language, String chatDownloadDir, String chatUploadDir) {
    String sql = persistence.getSQL(Persistence.KEY_WRITE_GLOBAL_ATTRIBUTES);
    logSQLKey(Persistence.KEY_WRITE_GLOBAL_ATTRIBUTES, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setInt(1, playervolume);
      prepstatement.setInt(2, iptvvolume);
      prepstatement.setInt(3, eis);
      prepstatement.setString(4, language);
      prepstatement.setString(5, chatDownloadDir);
      prepstatement.setString(6, chatUploadDir);
      prepstatement.setString(7, userid);
      prepstatement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
  }



  /**
   * KEEPALIVE ist eine Zeitscheibe und wird als Referenz für das Schließen der
   * Clientverbindung herangezogen.
   * 
   * 
   * @param uid
   *            dieser Anwender
   * 
   * @return {@code true}, die Zeitscheibe wurde angepasst
   * 
   * @throws SQLException
   *                      generischer SQL-Fehler
   */
  public boolean updateKEEPALIVE(String uid) {
    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_KEEPALIVE);
    logSQLKey(Persistence.KEY_UPDATE_KEEPALIVE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, uid);
      done = statement.executeUpdate() >= 1;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
    return done;
  }



  /**
   * Speichere eine Telefonkonferenzbeschreibung ab.
   *
   * @param userid
   *                              der Konferenzorganisator
   * @param konferenzname
   *                              der Konferenzname
   * @param konferenzbeschreibung
   *                              die genaue Beschreibung
   *
   * @return {@code true}, die Beschreibung konnte gespeichert werden
   *
   */
  public boolean updateKonferenzbeschreibung(String userid, String konferenzname,
      String konferenzbeschreibung) {
    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_WRITE_TELKO_DESCRIPTION);
    logSQLKey(Persistence.KEY_WRITE_TELKO_DESCRIPTION, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setString(1, konferenzbeschreibung);
      prepstatement.setString(2, konferenzname);
      prepstatement.setString(3, userid);
      done = prepstatement.executeUpdate() > 0;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return done;
  }



  /**
   * Finde alle User, die offline sind und in einer Telko sein sollen. Diese
   * Anwender müssen für den Konferenzraum als nicht anwesend gesetzt werden.
   *
   */
  public void updateKonferenzraumOnhook() {
    String sql = persistence.getSQL(Persistence.KEY_ORPHAINED_KONFERENZRAUM);
    logSQLKey(Persistence.KEY_ORPHAINED_KONFERENZRAUM, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {

      statement.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
  }



  /**
   * Der Anwender teilt mit, ober er den Telefonhörer auflegt oder abhebt.
   *
   * @param konferenzname
   *                      der Konferenzname
   * @param organisator
   *                      der Organisator
   * @param user
   *                      der Anwender, der seinen Onlinestatus setzt
   * @param offhook
   *                      {@code true}, der Anwender hat den Hörer abgenommen
   *
   * @return {@code true}, der Befehl wurde ausgeführt
   */
  public boolean updateKonferenzraumOnlinestatus(String konferenzname, String organisator, String user,
      boolean offhook) {

    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_KONFERENZRAUM_STATUS);
    logSQLKey(Persistence.KEY_UPDATE_KONFERENZRAUM_STATUS, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setBoolean(1, offhook);
      prepstatement.setString(2, konferenzname);
      prepstatement.setString(3, organisator);
      prepstatement.setString(4, user);
      prepstatement.executeUpdate();
      done = true;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return done;
  }



  /**
   * Der Anwender stellt seine Spracheinstellung ein.
   * 
   * @param language
   *                 diese Sprache
   * @param userid
   *                 dieser Anwender
   * @return {@code true}, die Spracheinstllung wurde übernommen
   * 
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   */
  public Language updateLanguage(Language language, String userid) throws DatabaseException {
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_LANGUAGE);
    logSQLKey(Persistence.KEY_UPDATE_LANGUAGE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setString(1, language.toString());
      prepstatement.setString(2, userid);
      prepstatement.executeUpdate();
      return language;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }

  }



  /**
   * Das Mikrofon wird auf stumm oder aufnehmen geschaltet.
   * 
   * @param mute
   *               {@code true} ist stumm
   * @param userid
   *               dieser Anwender
   * 
   * @throws SQLException
   *                      der SQL-Befehl konnte nicht ausgeführt werden
   */
  public void updateMute(boolean mute, String userid) throws SQLException {
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_MUTE);
    logSQLKey(Persistence.KEY_UPDATE_MUTE, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setBoolean(1, mute);
      statement.setString(2, userid);
      statement.executeUpdate();
    }

  }



  /**
   * Alle Tabellen werden auf den neuen Nickname umgestellt. Die Tabellen sind
   * TB_CHAT und TB_USER.
   *
   * @param uid
   *                      ein Anwender
   * @param neuerNickname
   *                      der neue Nickname
   */
  public void updateNickname(EntityUser entity) {
    String sqlUser = persistence.getSQL(Persistence.KEY_UPDATE_USER_NICKNAME);
    String sqlChat = persistence.getSQL(Persistence.KEY_UPDATE_OWNER_NICKNAME);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatementUser = connection.prepareStatement(sqlUser);
      PreparedStatement prepStatementChat = connection.prepareStatement(sqlChat)
    ) {

      logSQLKey(Persistence.KEY_UPDATE_USER_NICKNAME, sqlUser);
      prepStatementUser.setString(1, entity.getNickname());
      prepStatementUser.setString(2, entity.getUid());
      prepStatementUser.executeUpdate();

      logSQLKey(Persistence.KEY_UPDATE_OWNER_NICKNAME, sqlChat);
      prepStatementChat.setString(1, entity.getNickname());
      prepStatementChat.setString(2, entity.getUid());
      prepStatementChat.executeUpdate();

    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }

  }



  /**
   * Der Nickname wird aktualisiert. Änderungen werden nur in der Tabelle TB_USER
   * vorgenommen. Diese Methode wird bei einem {@code SIGNIN} aufgerufen, wenn der
   * Nickname aus der Mailadresse abgeleitet wird.
   * 
   * @param nickname
   *                 dieser Nickname
   * @param userid
   *                 dieser User
   * @return {@code true}, der Nickname wurde aktualisiert
   */
  public boolean updateNickname(String nickname, String userid) {
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_NICKNAME);
    logSQLKey(Persistence.KEY_UPDATE_NICKNAME, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      prepStatement.setString(1, nickname);
      prepStatement.setString(2, userid);
      return prepStatement.executeUpdate() >= 1;
    }
    catch (SQLException e) {
      log.warn(e.getMessage(), e);
      return false;
    }
  }



  /**
   * Aus diesem Verzeichnis werden PDX-Archive oder ODX-Dateien geladen.
   *
   * @param userid
   *                der ODX-Anwender
   * @param loaddir
   *                ein Verzeichnis oder eine Datei
   */
  public void updateOdxLoadDirectory(String userid, String loaddir) {
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_ODX_LASTDIR);
    logSQLKey(Persistence.KEY_UPDATE_ODX_LASTDIR, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {
      prepStatement.setString(1, loaddir);
      prepStatement.setString(2, userid);
      prepStatement.executeUpdate();
    }
    catch (SQLException e) {
      log.warn(e.getMessage(), e.getCause());
    }
  }



  /**
   * Der Projektor wird eingeschaltet oder ausgeschaltet. Der neue Zustand ist in
   * {@value user.onprojektor} enthalten
   *
   *
   * @param user
   *             dieser Anwender
   * @return {@code true}, der Projektor ist eingeschaltet
   */
  public boolean updateOnprojektor(EntityUser user) {
    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_WRITE_ONPROJEKTOR);
    logSQLKey(Persistence.KEY_WRITE_ONPROJEKTOR, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setBoolean(1, user.isOnprojektor());
      statement.setString(2, user.getUid());
      return statement.executeUpdate() > 0;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return done;
  }



  /**
   * Der Anwender teilt mit, ob er Übertragungen annimmt.
   *
   *
   * @param user
   *             ein Anwender
   * @return {@code true}, die Einstellungen konnten gespeichert werden
   */
  public boolean updateOnrekorder(EntityUser user) {
    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_WRITE_ONREKORDER);
    logSQLKey(Persistence.KEY_WRITE_ONREKORDER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setBoolean(1, user.isOnrekord());
      statement.setString(2, user.getUid());
      return statement.executeUpdate() > 0;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return done;
  }



  /**
   * Alle veralteten Programmversionen werden übergeben und gespeichert.
   *
   * @param list
   *             veraltete Programmversionen
   *
   * @return {@code true}, wurde ausgeführt
   */
  public boolean updateOutdated(List<Outdated> list) {
    if (list.size() == 0) return true; // es gibt keine veralteten Programmversionen
    boolean done = false;
    StringBuilder sql = new StringBuilder();
    sql.append("insert ignore into TB_OUTDATED (PROGRAMMVERSION) values ");
    for (Outdated outdated : list) {
      sql.append("('");
      sql.append(outdated.getProgrammversion());
      sql.append("'),");
    }
    sql.setLength(sql.length() - 1);
    logSQLKey("INSERT_OUTDATED", sql.toString());
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
    ) {
      statement.executeUpdate(sql.toString());
      done = true;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
    }
    return done;
  }



  /**
   * Die Methode wird nur von updateUser aufgerufen.
   *
   * @param nickname
   *                 der neue Nickname
   * @param uid
   *                 die Userid
   */
  public boolean updateOWNER(String uid, String nickname) {
    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_OWNER);
    logSQLKey(Persistence.KEY_UPDATE_OWNER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {
      statement.setString(1, nickname);
      statement.setString(2, uid);
      done = statement.executeUpdate() >= 1;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }
    return done;
  }



  /**
   * Der User hat einen neuen UDP-Port eingetragen, auf dem Telefonate
   * entgegengenommen werden.
   *
   *
   * @param userid
   *               ein User
   * @param port
   *               UDP-Port
   *
   * @return {@code true}, der Port wurde eingetragen
   */
  public boolean updatePhoneTelefonport(String userid, int port) {
    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_TELEFONPORT);
    logSQLKey(Persistence.KEY_UPDATE_TELEFONPORT, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setInt(1, port);
      statement.setString(2, userid);
      return statement.executeUpdate() > 0;

    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return done;
  }



  /**
   * Die Telefonlautstärke wird gespeichert. Die Telefonlautstärke sollte zwichen
   * 0 und 100 liegen.
   *
   * @param userid
   *               eine Benutzer-ID
   * @param volume
   *               ein Lautstärkewert
   */
  public void updatePhoneVolume(String userid, int volume) {
    String sql = persistence.getSQL(Persistence.KEY_PHONE_VOLUME);
    logSQLKey(Persistence.KEY_PHONE_VOLUME, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setInt(1, volume);
      statement.setString(2, userid);
      statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Die Mediaplayerlautstärke wird neu gespeichert.
   *
   * @param userid
   *                     eine Benutzer-ID
   * @param playervolume
   *                     ein Lautstärkewert
   * @return {@code true}, die Lautstärke konnte gespeichert werden
   *
   */
  public boolean updatePlayervolume(String userid, int playervolume) {
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_PLAYERVOLUME);
    logSQLKey(Persistence.KEY_UPDATE_PLAYERVOLUME, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setInt(1, playervolume);
      statement.setString(2, userid);
      return statement.executeUpdate() > 0;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return false;
  }



  /**
   * Die Rekordereinstellungen für die Bildschirmübertragung werden eingestellt.
   *
   *
   * @param user
   *             ein Anwender
   * @return {@code true}, die Einstellungen konnten gespeichert werden
   */
  public boolean updateRekorder(EntityUser user) {
    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_WRITE_REKORDER);
    logSQLKey(Persistence.KEY_WRITE_REKORDER, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setBoolean(1, user.isOnrekord());
      statement.setString(2, user.getUid());
      return statement.executeUpdate() > 0;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return done;
  }



  /**
   * Clientanfragen werden über den Port angenommen.
   *
   * @param userid
   *               der Anwender
   * @param port
   *               der Serverport
   * @return {@code true}, der Port konnte gespeichert werden
   */
  public boolean updateRekorderport(String userid, int port) {
    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_WRITE_REKORDERPORT);
    logSQLKey(Persistence.KEY_WRITE_REKORDERPORT, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setInt(1, port);
      statement.setString(2, userid);
      return statement.executeUpdate() > 0;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return done;
  }



  /**
   * Ein neues Schlüsselpaar wird gespeichert.
   * 
   * @return {@code true}, speichern war erfolgreich
   */
  public boolean updateRSA() {
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_RSA);
    logSQLKey(Persistence.KEY_UPDATE_RSA, sql);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {

      KeyPair keypair = Crypto.createRSAGenerator();
      PublicKey publicKey = keypair.getPublic();
      PrivateKey privateKey = keypair.getPrivate();

      byte[] pkcs8EncodedKey = privateKey.getEncoded();
      String base64PrivateKey = Base64.getEncoder().encodeToString(pkcs8EncodedKey);

      statement.setString(1, Base64.getEncoder().encodeToString(publicKey.getEncoded()));
      statement.setString(2, base64PrivateKey);
      return statement.executeUpdate() > 0;
    }
    catch (SQLException | NoSuchAlgorithmException e) {
      log.fatal(e.getMessage(), e.getCause());
      throw new DatabaseException(e.getMessage());
    }

  }



  /**
   * Der Anwender speichert seine Telefoneinstellungen.
   *
   *
   * @param user
   *             ein Anwender
   * @return {@code true}, die Einstellungen konnten gespeichert werden
   */
  public boolean updateTelefon(EntityUser user) {
    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_WRITE_TELEFON);
    logSQLKey(Persistence.KEY_WRITE_TELEFON, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setInt(1, user.getTelefonpuffer());
      statement.setString(2, user.getSpeaker());
      statement.setString(3, user.getMikrofon());
      statement.setString(4, user.getUid());
      return statement.executeUpdate() > 0;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return done;
  }



  /**
   * Diese Methode wird nach jedem Serverneustart aufgerufen. Für alle
   * Telkoteilnehmer werden die Mikrofone auf nicht stumm geschaltet.
   */
  public void updateUnmuteAll() {
    String sql = persistence.getSQL(Persistence.KEY_UNMUTE_ALL);
    logSQLKey(Persistence.KEY_UNMUTE_ALL, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement();
    ) {
      statement.executeUpdate(sql);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  public int updateUpdateport(String ip, int port) {
    int count = 0;
    String sql = persistence.getSQL(Persistence.KEY_UPDATE_PORT);
    logSQLKey(Persistence.KEY_UPDATE_PORT, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql);
    ) {
      statement.setInt(1, port);
      statement.setString(2, ip);
      count = statement.executeUpdate();
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return count;
  }



  /**
   * Ein Benutzer ändert seine Attribute.
   * 
   * @param identity
   *                        der verschlüsselte Text enthält USERID | EMAIL |
   *                        PASSWORD
   * @param userid
   *                        dieser Anwender
   * @param email
   *                        diese Mailadresse
   * @param password
   *                        dieses Passwort ist nicht verschlüsselt
   * @param nickname
   *                        dieser Nickname
   * @param foreGroundColor
   *                        die Vordergrundfarbe im Chat
   * @param backgroundColor
   *                        die Hintergrundfarbe im Chat
   * 
   * @return der User wird mit seinen aktuellen Einstellungen zurückgegeben oder
   *         {@code null}, wenn die Anpassungen nicht möglich waren
   */
  public UPDATEUSER updateUser(String identity, String userid, String email, String password, String nickname,
      Integer foreGroundColor, Integer backgroundColor) {
    if (email == null) {
      String isocode = fetchLanguage(userid);
      MultilingualString invalidMail = new MultilingualString(
          KEY.SERVER_EMAIL_INVALID, ISO639.fromValue(isocode)
      );
      UPDATEUSER error = new UPDATEUSER();
      error.setCommand(Command.UPDATEUSER);
      error.setHeader(ERROR);
      error.setDataset(Protocol.DATASET);
      error.setText(invalidMail.toString());
      return error;

    }
    if (!APODO.matcher(nickname).matches()) {

      String isocode = fetchLanguage(userid);
      MultilingualString unerlaubteZeichen = new MultilingualString(
          KEY.SERVER_UNERLAUBTE_ZEICHEN, ISO639.fromValue(isocode)
      );

      UPDATEUSER error = new UPDATEUSER();
      error.setCommand(Command.UPDATEUSER);
      error.setHeader(ERROR);
      error.setDataset(Protocol.DATASET);
      error.setText(unerlaubteZeichen.toString());
      return error;

    }

    if (password == null || password.length() < Constants.MIN_LEN_PASSWORD) {
      String isocode = fetchLanguage(userid);
      MultilingualString zeichenLangSein = new MultilingualString(
          KEY.SERVER_PASSWORT_ZU_KURZ, ISO639.fromValue(isocode)
      );
      String neu = zeichenLangSein.toString().replace("XXX", String.valueOf(Constants.MIN_LEN_PASSWORD));
      UPDATEUSER error = new UPDATEUSER();
      error.setCommand(Command.UPDATEUSER);
      error.setHeader(ERROR);
      error.setDataset(Protocol.DATASET);
      error.setText(neu);
      return error;
    }

//    Elige otro apodo. Choose a different nickname.

    // ist der Nickname erlaubt?
    List<String> verbotsliste = fetchForbiddenNicknames();
    String result = nickname.replaceAll("\\d", "").replaceAll("\\p{Punct}", "").replaceAll("\\p{Space}", "");
    for (String tmp : verbotsliste) {
      // Mit einer Fehlermldung zurück
      // Dieser Spitzname ist nicht erlaubt
      if (Functions.isStringInString(tmp, result)) {
        String isocode = fetchLanguage(userid);
        MultilingualString andererNickname = new MultilingualString(
            KEY.SERVER_NICKNAME_VERBOTEN, ISO639.fromValue(isocode)
        );
        UPDATEUSER error = new UPDATEUSER();
        error.setCommand(Command.UPDATEUSER);
        error.setHeader(ERROR);
        error.setDataset(Protocol.DATASET);
        error.setText(andererNickname.toString());
        return error;
      }
    }

    String sql = persistence.getSQL(Persistence.KEY_UPDATE_USER);

    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {

      statement.setString(1, email);
      statement.setString(2, nickname);
      statement.setInt(3, foreGroundColor);
      statement.setInt(4, backgroundColor);

      // MD5 Password
      String md5Password = BCrypt.withDefaults().hashToString(12, password.toCharArray());
      statement.setString(5, md5Password);
      statement.setString(6, userid);
      logSQLKey(Persistence.KEY_UPDATE_USER, sql);
      statement.executeUpdate();

      UPDATEUSER confirm = new UPDATEUSER();
      confirm.setCommand(Command.UPDATEUSER);
      confirm.setHeader(CONFIRM);
      confirm.setDataset(Protocol.DATASET);
      confirm.setNickname(nickname);
      confirm.setForegroundColor(foreGroundColor);
      confirm.setBackgroundColor(backgroundColor);
      confirm.setIdentity(identity);
      return confirm;

    }
    catch (SQLException e) {
      // NICKNAME Duplicate kann auftreten
      // Nickname vergeben
      // muss noch programmiert werden
      log.error(e.getMessage(), e.getCause());
      String isocode = fetchLanguage(userid);
      MultilingualString andererNickname = new MultilingualString(
          KEY.SERVER_ANDERER_NICKNAME, ISO639.fromValue(isocode)
      );
      UPDATEUSER error = new UPDATEUSER();
      error.setCommand(Command.UPDATEUSER);
      error.setHeader(ERROR);
      error.setDataset(Protocol.DATASET);
      error.setText(andererNickname.toString());
      return error;
    }
  }



  /**
   * Der Anwender hat seine Schwarze Liste aktualisiert.
   *
   *
   * @param userid
   *               die UserId
   * @param array
   *               die Schwarze List
   */
  public void updateUserBlacklist(String userid, BlacklistTypes[] array) {
    StringBuilder buffer = new StringBuilder();
    buffer.append("update TB_USER_BLACKLIST set DENY=(case");
    for (BlacklistTypes blacklist : array) {
      buffer.append(" when suffix='");
      buffer.append(blacklist.getSuffix()); // auf 1 oder 0 konvertieren
      buffer.append("' then ");
      buffer.append(blacklist.getChecked() ? 1 : 0); // auf 1 oder 0
                                                     // konvertieren
    }
    buffer.append(" end) where UID='");
    buffer.append(userid);
    buffer.append("'");
    String sql = buffer.toString();
    logSQLKey(Persistence.KEY_UPDATE_BLACKLIST, sql);
    try(
      Connection connection = pool.getConnection();
      Statement statement = connection.createStatement()
    ) {
      statement.executeUpdate(sql);

      for (BlacklistTypes blacklist : array) {
        if (blacklist.getChecked()) updateFiletransferList(userid, blacklist.getSuffix());
      }

    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Die Übertragung einer Datei erfolgt in zwei Schritte. Im ersten Teil werden
   * Absender, Empfänger, Dateiname und die Nachricht übertragen. Im zweiten Teil
   * wird die Datei selber übertragen.
   *
   *
   * @param uid
   *                         der Absender
   * @param receiver_uid
   *                         der Empfänger
   * @param absoluteFilename
   *                         der Dateiname mit Pfadangabe
   * @param filename
   *                         der Dateiname
   * @param filesize
   *                         die Dateigröße
   * @param message
   *                         eine Nachricht, die mit der Datei gesendet wurde
   *
   * @param remoteNickname
   *                         der Nickname des Empfängers
   *
   * @return dieser Datensatz steht in der Datenbank
   * 
   * @throws DatabaseException
   *                           der SQL-Befehl konnte nicht ausgeführt werden
   */
  public PrivateChatfile uploadPrivateChatfileAttributes(String uid, String receiver_uid,
      String absoluteFilename, String filename, Long filesize, String message, String remoteNickname)
      throws DatabaseException {

    PrivateChatfile entity = new PrivateChatfile();
    String sql = persistence.getSQL(Persistence.KEY_INSERT_PRIVATE_CHATFILE);
    logSQLKey(Persistence.KEY_INSERT_PRIVATE_CHATFILE, sql);

    if (filesize != null && filesize > Constants.MAX_FILESIZE) {
      throw new DatabaseException(
          String.valueOf(filesize) + " > " + String.valueOf(Constants.MAX_FILESIZE) + " - Filesize to big"
      );
    }

    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepStatement = connection.prepareStatement(sql)
    ) {

      Long chatfileNumber = createIdentifier();

      prepStatement.setLong(1, chatfileNumber);
      prepStatement.setString(2, receiver_uid);
      prepStatement.setString(3, uid);
      prepStatement.setObject(4, filesize, Types.BIGINT);
      prepStatement.setString(5, message);
      prepStatement.setString(6, absoluteFilename);
      prepStatement.setString(7, filename);
      prepStatement.setString(8, remoteNickname);
      prepStatement.executeUpdate();

      entity.setAbsoluteFilename(absoluteFilename);
      entity.setAnlage(chatfileNumber);
      entity.setFilename(filename); // optional
      entity.setFilesize(filesize); // optional
      entity.setMessage(message); // optional
      entity.setRemoteNickname(remoteNickname); // nickname
      entity.setReceiverUid(receiver_uid);
      entity.setUid(uid);
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e);
      throw new DatabaseException(e.getMessage());
    }
    return entity;
  }



  /**
   * Kann der Anwender angerufen werden?
   *
   * @param uid
   *               UserID
   * @param onCall
   *               {@code true} der Anwender möchte nicht angerufen werden;
   *               {@code false} der Anwender nimmt Anrufe entgegen
   * @return {@code true}, der SQL-Befehl ist ausführbar, sonst {@code false}
   *
   */
  public boolean userOnCall(String uid, boolean onCall) {
    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_USER_ONCALL);
    logSQLKey(Persistence.KEY_USER_ONCALL, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement statement = connection.prepareStatement(sql)
    ) {

      statement.setBoolean(1, onCall);
      statement.setString(2, uid);
      statement.executeUpdate();
      done = true;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return done;
  }



  /**
   * Alle Teilnehmer einer Telefonkonferenz werden gespeichert.
   *
   * @param organisatorUid
   *                         der Organisator muss immer mitgespeichert werden.
   * @param konferenzraum
   *                         der Konferenzname
   * @param teilnehmerUserid
   *                         die userIds der Teilnehmer
   *
   * @return {@code true}, die Teilnehmer wurden gespeichert
   * 
   * @throws DatabaseException
   *                           der Teilnehmer konnte nicht in die Telefonkonferenz
   *                           aufgenommen werden
   */
  public void writeKonferenzteilnehmer(String organisatorUid, String konferenzraum,
      Collection<String> teilnehmerUserid) throws DatabaseException {

    // Wenn der Organisator in der teilnehmerUserid fehlt, dann wird er hinzugepackt
    if (!teilnehmerUserid.contains(organisatorUid)) teilnehmerUserid.add(organisatorUid);

    // Lösche alle Teilnehmer(UID) aus der Tabelle TB_KONFERENZRAUM, die nicht in
    // der Collection teilnehmerUserid sind.

    try {
      deleteTeilnehmer(konferenzraum, organisatorUid, teilnehmerUserid);
    }
    catch (DatabaseException e) {
      throw new DatabaseException(
          teilnehmerUserid + " - dieser Konferenzteilnehmer konnte nicht gelöscht werden."
      );
    }
    // Mache ein Insert für alle teilnehmerUserid, die nicht in der Tabelle
    // TB_KONFERENZRAUM (KONFERNZRAUM/UID) sind.
    try {
      insertTeilnehmer(konferenzraum, organisatorUid, teilnehmerUserid);
    }
    catch (SQLException e) {
      throw new DatabaseException(
          teilnehmerUserid
              + " - dieser Konferenzteilnehmer konnte nicht in die Telefonkonferenz aufgenommen werden."
      );
    }
  }



  /**
   *
   *
   *
   * @param uid
   * @param stream
   */
  public void writeStream(String uid, InputStream stream) {

  }



  /**
   *
   * @param userid
   *                      der Konferenzorganisator
   * @param konferenzname
   *                      der Konferenzname
   * @param datetime
   *                      das Verfallsdatum
   *
   * @return {@code true}, das Verfallsdatum konnte gespeichert werden
   */
  public boolean writeVerfallsdatum(String userid, String konferenzname, LocalDateTime datetime) {
    boolean done = false;
    String sql = persistence.getSQL(Persistence.KEY_WRITE_VERFALLSDATUM);
    logSQLKey(Persistence.KEY_WRITE_VERFALLSDATUM, sql);
    try(
      Connection connection = pool.getConnection();
      PreparedStatement prepstatement = connection.prepareStatement(sql)
    ) {

      prepstatement.setTimestamp(1, Timestamp.valueOf(datetime));
      prepstatement.setString(2, konferenzname);
      prepstatement.setString(3, userid);
      done = prepstatement.executeUpdate() > 0;
    }
    catch (SQLException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return done;
  }

}
