content: /*
   * Copyright (C) 2013 Glyptodon LLC
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   * THE SOFTWARE.
   */
  
  package net.sourceforge.guacamole.net.auth.mysql.service;
  
  import com.google.inject.Inject;
  import com.google.inject.Provider;
  import java.util.ArrayList;
  import java.util.Date;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import org.glyptodon.guacamole.GuacamoleException;
  import org.glyptodon.guacamole.net.GuacamoleSocket;
  import org.glyptodon.guacamole.net.InetGuacamoleSocket;
  import org.glyptodon.guacamole.net.SSLGuacamoleSocket;
  import net.sourceforge.guacamole.net.auth.mysql.ActiveConnectionMap;
  import net.sourceforge.guacamole.net.auth.mysql.MySQLConnection;
  import net.sourceforge.guacamole.net.auth.mysql.MySQLConnectionRecord;
  import net.sourceforge.guacamole.net.auth.mysql.MySQLGuacamoleSocket;
  import net.sourceforge.guacamole.net.auth.mysql.dao.ConnectionHistoryMapper;
  import net.sourceforge.guacamole.net.auth.mysql.dao.ConnectionMapper;
  import net.sourceforge.guacamole.net.auth.mysql.dao.ConnectionParameterMapper;
  import net.sourceforge.guacamole.net.auth.mysql.model.Connection;
  import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionExample;
  import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionExample.Criteria;
  import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionHistory;
  import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionHistoryExample;
  import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionParameter;
  import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionParameterExample;
  import net.sourceforge.guacamole.net.auth.mysql.properties.MySQLGuacamoleProperties;
  import org.glyptodon.guacamole.properties.GuacamoleProperties;
  import org.glyptodon.guacamole.protocol.ConfiguredGuacamoleSocket;
  import org.glyptodon.guacamole.protocol.GuacamoleClientInformation;
  import org.glyptodon.guacamole.protocol.GuacamoleConfiguration;
  import org.apache.ibatis.session.RowBounds;
  import org.glyptodon.guacamole.GuacamoleClientTooManyException;
  import org.glyptodon.guacamole.GuacamoleResourceConflictException;
  
  /**
   * Service which provides convenience methods for creating, retrieving, and
   * manipulating connections.
   *
   * @author Michael Jumper, James Muehlner
   */
  public class ConnectionService {
  
      /**
       * DAO for accessing connections.
       */
      @Inject
      private ConnectionMapper connectionDAO;
  
      /**
       * DAO for accessing connection parameters.
       */
      @Inject
      private ConnectionParameterMapper connectionParameterDAO;
  
      /**
       * DAO for accessing connection history.
       */
      @Inject
      private ConnectionHistoryMapper connectionHistoryDAO;
  
      /**
       * Provider which creates MySQLConnections.
       */
      @Inject
      private Provider<MySQLConnection> mySQLConnectionProvider;
  
      /**
       * Provider which creates MySQLGuacamoleSockets.
       */
      @Inject
      private Provider<MySQLGuacamoleSocket> mySQLGuacamoleSocketProvider;
  
      /**
       * Map of all currently active connections.
       */
      @Inject
      private ActiveConnectionMap activeConnectionMap;
  
      /**
       * Service managing users.
       */
      @Inject
      private UserService userService;
  
      /**
       * Retrieves the connection having the given name from the database.
       *
       * @param name The name of the connection to return.
       * @param parentID The ID of the parent connection group.
       * @param userID The ID of the user who queried this connection.
       * @return The connection having the given name, or null if no such
       *         connection could be found.
       */
      public MySQLConnection retrieveConnection(String name, Integer parentID,
              int userID) {
  
          // Create criteria
          ConnectionExample example = new ConnectionExample();
          Criteria criteria = example.createCriteria().andConnection_nameEqualTo(name);
          if(parentID != null)
              criteria.andParent_idEqualTo(parentID);
          else
              criteria.andParent_idIsNull();
          
          // Query connection by name and parentID
          List<Connection> connections =
                  connectionDAO.selectByExample(example);
  
          // If no connection found, return null
          if(connections.isEmpty())
              return null;
  
          // Otherwise, return found connection
          return toMySQLConnection(connections.get(0), userID);
  
      }
  
      /**
       * Retrieves the connection having the given unique identifier 
       * from the database.
       *
       * @param uniqueIdentifier The unique identifier of the connection to retrieve.
       * @param userID The ID of the user who queried this connection.
       * @return The connection having the given unique identifier, 
       *         or null if no such connection was found.
       */
      public MySQLConnection retrieveConnection(String uniqueIdentifier, int userID) {
  
          // The unique identifier for a MySQLConnection is the database ID
          int connectionID;
          try {
              connectionID = Integer.parseInt(uniqueIdentifier);
          } catch(NumberFormatException e) {
              // Invalid number means it can't be a DB record; not found
              return null;
          }
          
          return retrieveConnection(connectionID, userID);
      }
  
      /**
       * Retrieves the connection having the given ID from the database.
       *
       * @param id The ID of the connection to retrieve.
       * @param userID The ID of the user who queried this connection.
       * @return The connection having the given ID, or null if no such
       *         connection was found.
       */
      public MySQLConnection retrieveConnection(int id, int userID) {
  
          // Query connection by ID
          Connection connection = connectionDAO.selectByPrimaryKey(id);
  
          // If no connection found, return null
          if(connection == null)
              return null;
  
          // Otherwise, return found connection
          return toMySQLConnection(connection, userID);
      }
      
      /**
       * Returns a list of the IDs of all connections with a given parent ID.
       * @param parentID The ID of the parent for all the queried connections.
       * @return a list of the IDs of all connections with a given parent ID.
       */
      public List<Integer> getAllConnectionIDs(Integer parentID) {
          
          // Create criteria
          ConnectionExample example = new ConnectionExample();
          Criteria criteria = example.createCriteria();
          
          if(parentID != null)
              criteria.andParent_idEqualTo(parentID);
          else
              criteria.andParent_idIsNull();
          
          // Query the connections
          List<Connection> connections = connectionDAO.selectByExample(example);
          
          // List of IDs of connections with the given parent
          List<Integer> connectionIDs = new ArrayList<Integer>();
          
          for(Connection connection : connections) {
              connectionIDs.add(connection.getConnection_id());
          }
          
          return connectionIDs;
      }
  
      /**
       * Convert the given database-retrieved Connection into a MySQLConnection.
       * The parameters of the given connection will be read and added to the
       * MySQLConnection in the process.
       *
       * @param connection The connection to convert.
       * @param userID The user who queried this connection.
       * @return A new MySQLConnection containing all data associated with the
       *         specified connection.
       */
      private MySQLConnection toMySQLConnection(Connection connection, int userID) {
  
          // Build configuration
          GuacamoleConfiguration config = new GuacamoleConfiguration();
  
          // Query parameters for configuration
          ConnectionParameterExample connectionParameterExample = new ConnectionParameterExample();
          connectionParameterExample.createCriteria().andConnection_idEqualTo(connection.getConnection_id());
          List<ConnectionParameter> connectionParameters =
                  connectionParameterDAO.selectByExample(connectionParameterExample);
  
          // Set protocol
          config.setProtocol(connection.getProtocol());
  
          // Set all values for all parameters
          for (ConnectionParameter parameter : connectionParameters)
              config.setParameter(parameter.getParameter_name(),
                      parameter.getParameter_value());
  
          // Create new MySQLConnection from retrieved data
          MySQLConnection mySQLConnection = mySQLConnectionProvider.get();
          mySQLConnection.init(
              connection.getConnection_id(),
              connection.getParent_id(),
              connection.getConnection_name(),
              Integer.toString(connection.getConnection_id()),
              config,
              retrieveHistory(connection.getConnection_id()),
              userID
          );
  
          return mySQLConnection;
  
      }
  
      /**
       * Retrieves the history of the connection having the given ID.
       *
       * @param connectionID The ID of the connection to retrieve the history of.
       * @return A list of MySQLConnectionRecord documenting the history of this
       *         connection.
       */
      public List<MySQLConnectionRecord> retrieveHistory(int connectionID) {
  
          // Retrieve history records relating to given connection ID
          ConnectionHistoryExample example = new ConnectionHistoryExample();
          example.createCriteria().andConnection_idEqualTo(connectionID);
  
          // We want to return the newest records first
          example.setOrderByClause("start_date DESC");
  
          // Set the maximum number of history records returned to 100
          RowBounds rowBounds = new RowBounds(0, 100);
  
          // Retrieve all connection history entries
          List<ConnectionHistory> connectionHistories =
                  connectionHistoryDAO.selectByExampleWithRowbounds(example, rowBounds);
  
          // Convert history entries to connection records
          List<MySQLConnectionRecord> connectionRecords = new ArrayList<MySQLConnectionRecord>();
          Set<Integer> userIDSet = new HashSet<Integer>();
          for(ConnectionHistory history : connectionHistories) {
              userIDSet.add(history.getUser_id());
          }
  
          // Determine whether connection is currently active
          int user_count = activeConnectionMap.getCurrentUserCount(connectionID);
  
          // Get all the usernames for the users who are in the history
          Map<Integer, String> usernameMap = userService.retrieveUsernames(userIDSet);
  
          // Create the new ConnectionRecords
          for(ConnectionHistory history : connectionHistories) {
  
              Date startDate = history.getStart_date();
              Date endDate = history.getEnd_date();
              String username = usernameMap.get(history.getUser_id());
  
              // If there are active users, list the top N not-ended connections
              // as active (best guess)
              MySQLConnectionRecord connectionRecord;
              if (user_count > 0 && endDate == null) {
                  connectionRecord = new MySQLConnectionRecord(startDate, endDate, username, true);
                  user_count--;
              }
  
              // If no active users, or end date is recorded, connection is not
              // active.
              else
                  connectionRecord = new MySQLConnectionRecord(startDate, endDate, username, false);
  
              connectionRecords.add(connectionRecord);
  
          }
  
          return connectionRecords;
      }
      
      
  
      /**
       * Create a MySQLGuacamoleSocket using the provided connection.
       *
       * @param connection The connection to use when connecting the socket.
       * @param info The information to use when performing the connection
       *             handshake.
       * @param userID The ID of the user who is connecting to the socket.
       * @param connectionGroupID The ID of the balancing connection group that is
       *                          being connected to; null if not used.
       * @return The connected socket.
       * @throws GuacamoleException If an error occurs while connecting the
       *                            socket.
       */
      public MySQLGuacamoleSocket connect(MySQLConnection connection,
              GuacamoleClientInformation info, int userID, Integer connectionGroupID)
          throws GuacamoleException {
  
          synchronized (activeConnectionMap) {
  
              // If the given connection is active, and multiple simultaneous
              // connections are not allowed, disallow connection
              if(GuacamoleProperties.getProperty(
                      MySQLGuacamoleProperties.MYSQL_DISALLOW_SIMULTANEOUS_CONNECTIONS, false)
                      && activeConnectionMap.isActive(connection.getConnectionID()))
                  throw new GuacamoleResourceConflictException("Cannot connect. This connection is in use.");
              
              if(GuacamoleProperties.getProperty(
                      MySQLGuacamoleProperties.MYSQL_DISALLOW_DUPLICATE_CONNECTIONS, true)
                      && activeConnectionMap.isConnectionUserActive(connection.getConnectionID(), userID))
                  throw new GuacamoleClientTooManyException
                          ("Cannot connect. Connection already in use by this user.");
  
              // Get guacd connection information
              String host = GuacamoleProperties.getRequiredProperty(GuacamoleProperties.GUACD_HOSTNAME);
              int port = GuacamoleProperties.getRequiredProperty(GuacamoleProperties.GUACD_PORT);
  
              // Get socket
              GuacamoleSocket socket;
              if (GuacamoleProperties.getProperty(GuacamoleProperties.GUACD_SSL, false))
                  socket = new ConfiguredGuacamoleSocket(
                      new SSLGuacamoleSocket(host, port),
                      connection.getConfiguration(), info
                  );
              else
                  socket = new ConfiguredGuacamoleSocket(
                      new InetGuacamoleSocket(host, port),
                      connection.getConfiguration(), info
                  );
  
              // Mark this connection as active
              int historyID = activeConnectionMap.openConnection(connection.getConnectionID(), 
                      userID, connectionGroupID);
  
                  // Return new MySQLGuacamoleSocket
              MySQLGuacamoleSocket mySQLGuacamoleSocket = mySQLGuacamoleSocketProvider.get();
              mySQLGuacamoleSocket.init(socket, historyID, connectionGroupID);
                  
              return mySQLGuacamoleSocket;
  
          }
  
      }
  
      /**
       * Creates a new connection having the given name and protocol.
       *
       * @param name The name to assign to the new connection.
       * @param protocol The protocol to assign to the new connection.
       * @param userID The ID of the user who created this connection.
       * @param parentID The ID of the parent connection group.
       * @return A new MySQLConnection containing the data of the newly created
       *         connection.
       */
      public MySQLConnection createConnection(String name, String protocol,
              int userID, Integer parentID) {
  
          // Initialize database connection
          Connection connection = new Connection();
          connection.setConnection_name(name);
          connection.setProtocol(protocol);
          connection.setParent_id(parentID);
  
          // Create connection
          connectionDAO.insert(connection);
          return toMySQLConnection(connection, userID);
  
      }
  
      /**
       * Deletes the connection having the given ID from the database.
       * @param id The ID of the connection to delete.
       */
      public void deleteConnection(int id) {
          connectionDAO.deleteByPrimaryKey(id);
      }
  
      /**
       * Updates the connection in the database corresponding to the given
       * MySQLConnection.
       *
       * @param mySQLConnection The MySQLConnection to update (save) to the
       *                        database. This connection must already exist.
       */
      public void updateConnection(MySQLConnection mySQLConnection) {
  
          // Populate connection
          Connection connection = new Connection();
          connection.setConnection_id(mySQLConnection.getConnectionID());
          connection.setParent_id(mySQLConnection.getParentID());
          connection.setConnection_name(mySQLConnection.getName());
          connection.setProtocol(mySQLConnection.getConfiguration().getProtocol());
  
          // Update the connection in the database
          connectionDAO.updateByPrimaryKey(connection);
  
      }
  
      /**
       * Get the identifiers of all the connections defined in the system 
       * with a certain parentID.
       *
       * @return A Set of identifiers of all the connections defined in the system
       * with the given parentID.
       */
      public Set<String> getAllConnectionIdentifiers(Integer parentID) {
  
          // Set of all present connection identifiers
          Set<String> identifiers = new HashSet<String>();
          
          // Set up Criteria
          ConnectionExample example = new ConnectionExample();
          Criteria criteria = example.createCriteria();
          if(parentID != null)
              criteria.andParent_idEqualTo(parentID);
          else
              criteria.andParent_idIsNull();
  
          // Query connection identifiers
          List<Connection> connections =
                  connectionDAO.selectByExample(example);
          for (Connection connection : connections)
              identifiers.add(String.valueOf(connection.getConnection_id()));
  
          return identifiers;
  
      }
  
      /**
       * Get the connection IDs of all the connections defined in the system 
       * with a certain parent connection group.
       *
       * @return A list of connection IDs of all the connections defined in the system.
       */
      public List<Integer> getAllConnectionIDs() {
  
          // Set of all present connection IDs
          List<Integer> connectionIDs = new ArrayList<Integer>();
  
          // Create the criteria
          ConnectionExample example = new ConnectionExample();
          
          // Query the connections
          List<Connection> connections =
                  connectionDAO.selectByExample(example);
          for (Connection connection : connections)
              connectionIDs.add(connection.getConnection_id());
  
          return connectionIDs;
  
      }
  
  }
  