/*
* 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.HashSet;
import java.util.List;
import java.util.Set;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.GuacamoleSocket;
import net.sourceforge.guacamole.net.auth.mysql.ActiveConnectionMap;
import net.sourceforge.guacamole.net.auth.mysql.MySQLConnection;
import net.sourceforge.guacamole.net.auth.mysql.MySQLConnectionGroup;
import net.sourceforge.guacamole.net.auth.mysql.MySQLConstants;
import net.sourceforge.guacamole.net.auth.mysql.dao.ConnectionGroupMapper;
import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionGroup;
import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionGroupExample;
import net.sourceforge.guacamole.net.auth.mysql.model.ConnectionGroupExample.Criteria;
import net.sourceforge.guacamole.net.auth.mysql.properties.MySQLGuacamoleProperties;
import org.glyptodon.guacamole.GuacamoleClientTooManyException;
import org.glyptodon.guacamole.GuacamoleResourceNotFoundException;
import org.glyptodon.guacamole.GuacamoleServerBusyException;
import org.glyptodon.guacamole.properties.GuacamoleProperties;
import org.glyptodon.guacamole.protocol.GuacamoleClientInformation;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating connection groups.
*
* @author James Muehlner
*/
public class ConnectionGroupService {
/**
* Service for managing connections.
*/
@Inject
private ConnectionService connectionService;
/**
* DAO for accessing connection groups.
*/
@Inject
private ConnectionGroupMapper connectionGroupDAO;
/**
* Provider which creates MySQLConnectionGroups.
*/
@Inject
private Provider<MySQLConnectionGroup> mysqlConnectionGroupProvider;
/**
* The map of all active connections.
*/
@Inject
private ActiveConnectionMap activeConnectionMap;
/**
* Retrieves the connection group 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 group.
* @return The connection having the given name, or null if no such
* connection group could be found.
*/
public MySQLConnectionGroup retrieveConnectionGroup(String name, Integer parentID,
int userID) {
// Create criteria
ConnectionGroupExample example = new ConnectionGroupExample();
Criteria criteria = example.createCriteria().andConnection_group_nameEqualTo(name);
if(parentID != null)
criteria.andParent_idEqualTo(parentID);
else
criteria.andParent_idIsNull();
// Query connection group by name and parentID
List<ConnectionGroup> connectionGroups =
connectionGroupDAO.selectByExample(example);
// If no connection group found, return null
if(connectionGroups.isEmpty())
return null;
// Otherwise, return found connection
return toMySQLConnectionGroup(connectionGroups.get(0), userID);
}
/**
* Retrieves the connection group having the given unique identifier
* from the database.
*
* @param uniqueIdentifier The unique identifier of the connection group to retrieve.
* @param userID The ID of the user who queried this connection group.
* @return The connection group having the given unique identifier,
* or null if no such connection group was found.
*/
public MySQLConnectionGroup retrieveConnectionGroup(String uniqueIdentifier,
int userID) throws GuacamoleException {
// The unique identifier for a MySQLConnectionGroup is the database ID
Integer connectionGroupID = null;
// Try to parse the connectionID if it's not the root group
if(!MySQLConstants.CONNECTION_GROUP_ROOT_IDENTIFIER.equals(uniqueIdentifier)) {
try {
connectionGroupID = Integer.parseInt(uniqueIdentifier);
} catch(NumberFormatException e) {
throw new GuacamoleResourceNotFoundException("Invalid connection group ID.");
}
}
return retrieveConnectionGroup(connectionGroupID, userID);
}
/**
* Retrieves the connection group having the given ID from the database.
*
* @param id The ID of the connection group to retrieve.
* @param userID The ID of the user who queried this connection.
* @return The connection group having the given ID, or null if no such
* connection was found.
*/
public MySQLConnectionGroup retrieveConnectionGroup(Integer id, int userID) {
// This is the root connection group, so just create it here
if(id == null) {
MySQLConnectionGroup connectionGroup = mysqlConnectionGroupProvider.get();
connectionGroup.init(null, null,
MySQLConstants.CONNECTION_GROUP_ROOT_IDENTIFIER,
MySQLConstants.CONNECTION_GROUP_ROOT_IDENTIFIER,
org.glyptodon.guacamole.net.auth.ConnectionGroup.Type.ORGANIZATIONAL,
userID);
return connectionGroup;
}
// Query connection by ID
ConnectionGroup connectionGroup = connectionGroupDAO.selectByPrimaryKey(id);
// If no connection found, return null
if(connectionGroup == null)
return null;
// Otherwise, return found connection
return toMySQLConnectionGroup(connectionGroup, userID);
}
/**
* Connect to the connection within the given group with the lowest number
* of currently active users.
*
* @param group The group to load balance across.
* @param info The information to use when performing the connection
* handshake.
* @param userID The ID of the user who is connecting to the socket.
* @return The connected socket.
* @throws GuacamoleException If an error occurs while connecting the
* socket.
*/
public GuacamoleSocket connect(MySQLConnectionGroup group,
GuacamoleClientInformation info, int userID) throws GuacamoleException {
// Get all connections in the group.
List<Integer> connectionIDs = connectionService.getAllConnectionIDs
(group.getConnectionGroupID());
synchronized (activeConnectionMap) {
// Get the least used connection.
Integer leastUsedConnectionID =
activeConnectionMap.getLeastUsedConnection(connectionIDs);
if(leastUsedConnectionID == null)
throw new GuacamoleResourceNotFoundException("No connections found in group.");
if(GuacamoleProperties.getProperty(
MySQLGuacamoleProperties.MYSQL_DISALLOW_SIMULTANEOUS_CONNECTIONS, false)
&& activeConnectionMap.isActive(leastUsedConnectionID))
throw new GuacamoleServerBusyException
("Cannot connect. All connections are in use.");
if(GuacamoleProperties.getProperty(
MySQLGuacamoleProperties.MYSQL_DISALLOW_DUPLICATE_CONNECTIONS, true)
&& activeConnectionMap.isConnectionGroupUserActive(group.getConnectionGroupID(), userID))
throw new GuacamoleClientTooManyException
("Cannot connect. Connection group already in use by this user.");
// Get the connection
MySQLConnection connection = connectionService
.retrieveConnection(leastUsedConnectionID, userID);
// Connect to the connection
return connectionService.connect(connection, info, userID, group.getConnectionGroupID());
}
}
/**
* Returns a list of the IDs of all connection groups with a given parent ID.
* @param parentID The ID of the parent for all the queried connection groups.
* @return a list of the IDs of all connection groups with a given parent ID.
*/
public List<Integer> getAllConnectionGroupIDs(Integer parentID) {
// Create criteria
ConnectionGroupExample example = new ConnectionGroupExample();
Criteria criteria = example.createCriteria();
if(parentID != null)
criteria.andParent_idEqualTo(parentID);
else
criteria.andParent_idIsNull();
// Query the connections
List<ConnectionGroup> connectionGroups = connectionGroupDAO.selectByExample(example);
// List of IDs of connections with the given parent
List<Integer> connectionGroupIDs = new ArrayList<Integer>();
for(ConnectionGroup connectionGroup : connectionGroups) {
connectionGroupIDs.add(connectionGroup.getConnection_group_id());
}
return connectionGroupIDs;
}
/**
* Get the identifiers of all the connection groups defined in the system
* with a certain parentID.
*
* @return A Set of identifiers of all the connection groups defined
* in the system with the given parentID.
*/
public Set<String> getAllConnectionGroupIdentifiers(Integer parentID) {
// Set of all present connection identifiers
Set<String> identifiers = new HashSet<String>();
// Set up Criteria
ConnectionGroupExample example = new ConnectionGroupExample();
Criteria criteria = example.createCriteria();
if(parentID != null)
criteria.andParent_idEqualTo(parentID);
else
criteria.andParent_idIsNull();
// Query connection identifiers
List<ConnectionGroup> connectionGroups =
connectionGroupDAO.selectByExample(example);
for (ConnectionGroup connectionGroup : connectionGroups)
identifiers.add(String.valueOf(connectionGroup.getConnection_group_id()));
return identifiers;
}
/**
* 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 MySQLConnectionGroup toMySQLConnectionGroup(ConnectionGroup connectionGroup, int userID) {
// Create new MySQLConnection from retrieved data
MySQLConnectionGroup mySQLConnectionGroup = mysqlConnectionGroupProvider.get();
String mySqlType = connectionGroup.getType();
org.glyptodon.guacamole.net.auth.ConnectionGroup.Type authType;
if(mySqlType.equals(MySQLConstants.CONNECTION_GROUP_ORGANIZATIONAL))
authType = org.glyptodon.guacamole.net.auth.ConnectionGroup.Type.ORGANIZATIONAL;
else
authType = org.glyptodon.guacamole.net.auth.ConnectionGroup.Type.BALANCING;
mySQLConnectionGroup.init(
connectionGroup.getConnection_group_id(),
connectionGroup.getParent_id(),
connectionGroup.getConnection_group_name(),
Integer.toString(connectionGroup.getConnection_group_id()),
authType,
userID
);
return mySQLConnectionGroup;
}
/**
* Get the connection group IDs of all the connection groups defined in the system.
*
* @return A list of connection group IDs of all the connection groups defined in the system.
*/
public List<Integer> getAllConnectionGroupIDs() {
// Set of all present connection group IDs
List<Integer> connectionGroupIDs = new ArrayList<Integer>();
// Query all connection IDs
List<ConnectionGroup> connections =
connectionGroupDAO.selectByExample(new ConnectionGroupExample());
for (ConnectionGroup connection : connections)
connectionGroupIDs.add(connection.getConnection_group_id());
return connectionGroupIDs;
}
/**
* Creates a new connection group having the given name and type.
*
* @param name The name to assign to the new connection group.
* @param userID The ID of the user who created this connection group.
* @param Type The type of the new connection group.
* @return A new MySQLConnectionGroup containing the data of the newly created
* connection group.
*/
public MySQLConnectionGroup createConnectionGroup(String name, int userID,
Integer parentID, String type) {
// Initialize database connection
ConnectionGroup connectionGroup = new ConnectionGroup();
connectionGroup.setConnection_group_name(name);
connectionGroup.setParent_id(parentID);
connectionGroup.setType(type);
// Create connection
connectionGroupDAO.insert(connectionGroup);
return toMySQLConnectionGroup(connectionGroup, userID);
}
/**
* Updates the connection group in the database corresponding to the given
* MySQLConnectionGroup.
*
* @param mySQLConnectionGroup The MySQLConnectionGroup to update (save)
* to the database.
* This connection must already exist.
*/
public void updateConnectionGroup(MySQLConnectionGroup mySQLConnectionGroup) {
// Populate connection
ConnectionGroup connectionGroup = new ConnectionGroup();
connectionGroup.setConnection_group_id(mySQLConnectionGroup.getConnectionGroupID());
connectionGroup.setParent_id(mySQLConnectionGroup.getParentID());
connectionGroup.setConnection_group_name(mySQLConnectionGroup.getName());
switch(mySQLConnectionGroup.getType()) {
case BALANCING :
connectionGroup.setType(MySQLConstants.CONNECTION_GROUP_BALANCING);
break;
case ORGANIZATIONAL:
connectionGroup.setType(MySQLConstants.CONNECTION_GROUP_ORGANIZATIONAL);
break;
}
// Update the connection group in the database
connectionGroupDAO.updateByPrimaryKey(connectionGroup);
}
/**
* Deletes the connection group having the given ID from the database.
* @param id The ID of the connection group to delete.
*/
public void deleteConnectionGroup(int id) {
connectionGroupDAO.deleteByPrimaryKey(id);
}
}