diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/socket/AbstractGuacamoleSocketService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/socket/AbstractGuacamoleSocketService.java index 036dbda7c..78f4d61ca 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/socket/AbstractGuacamoleSocketService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/socket/AbstractGuacamoleSocketService.java @@ -28,10 +28,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; -import java.util.HashMap; -import java.util.LinkedList; import java.util.List; -import java.util.Map; import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser; import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection; import org.glyptodon.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup; @@ -98,63 +95,14 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS private ConnectionRecordMapper connectionRecordMapper; /** - * The current number of concurrent uses of the connection having a given - * identifier. + * All active connections to a connection having a given identifier. */ - private final Map> activeConnections = - new HashMap>(); + private final ActiveConnectionMultimap activeConnections = new ActiveConnectionMultimap(); /** - * Atomically increments the current usage count for the given connection. - * - * @param connection - * The connection which is being used. + * All active connections to a connection group having a given identifier. */ - private void addActiveConnection(Connection connection, ConnectionRecord record) { - synchronized (activeConnections) { - - String identifier = connection.getIdentifier(); - - // Get set of active connection records, creating if necessary - LinkedList connections = activeConnections.get(identifier); - if (connections == null) { - connections = new LinkedList(); - activeConnections.put(identifier, connections); - } - - // Add active connection - connections.addFirst(record); - - } - } - - /** - * Atomically decrements the current usage count for the given connection. - * If a combination of incrementUsage() and decrementUsage() calls result - * in the usage counter being reduced to zero, it is guaranteed that one - * of those decrementUsage() calls will remove the value from the map. - * - * @param connection - * The connection which is no longer being used. - */ - private void removeActiveConnection(Connection connection, ConnectionRecord record) { - synchronized (activeConnections) { - - String identifier = connection.getIdentifier(); - - // Get set of active connection records - LinkedList connections = activeConnections.get(identifier); - assert(connections != null); - - // Remove old record - connections.remove(record); - - // If now empty, clean the tracking entry - if (connections.isEmpty()) - activeConnections.remove(identifier); - - } - } + private final ActiveConnectionMultimap activeConnectionGroups = new ActiveConnectionMultimap(); /** * Acquires possibly-exclusive access to any one of the given connections @@ -223,6 +171,10 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS // Create record for active connection final ActiveConnectionRecord activeConnection = new ActiveConnectionRecord(user); + + // Get relevant identifiers + final String identifier = connection.getIdentifier(); + final String parentIdentifier = connection.getParentIdentifier(); // Generate configuration from available data GuacamoleConfiguration config = new GuacamoleConfiguration(); @@ -232,7 +184,7 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS config.setProtocol(model.getProtocol()); // Set parameters from associated data - Collection parameters = parameterMapper.select(connection.getIdentifier()); + Collection parameters = parameterMapper.select(identifier); for (ParameterModel parameter : parameters) config.setParameter(parameter.getName(), parameter.getValue()); @@ -247,7 +199,8 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS try { // Record new active connection - addActiveConnection(connection, activeConnection); + activeConnections.put(identifier, activeConnection); + activeConnectionGroups.put(parentIdentifier, activeConnection); // Return newly-reserved connection return new ConfiguredGuacamoleSocket( @@ -265,7 +218,8 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS super.close(); // Release connection upon close - removeActiveConnection(connection, activeConnection); + activeConnections.remove(identifier, activeConnection); + activeConnectionGroups.remove(parentIdentifier, activeConnection); release(user, connection); UserModel userModel = user.getUser().getModel(); @@ -274,7 +228,7 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS // Copy user information and timestamps into new record recordModel.setUserID(userModel.getObjectID()); recordModel.setUsername(userModel.getIdentifier()); - recordModel.setConnectionIdentifier(connection.getIdentifier()); + recordModel.setConnectionIdentifier(identifier); recordModel.setStartDate(activeConnection.getStartDate()); recordModel.setEndDate(new Date()); @@ -291,7 +245,8 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS catch (GuacamoleException e) { // Atomically release access to connection - removeActiveConnection(connection, activeConnection); + activeConnections.remove(identifier, activeConnection); + activeConnectionGroups.remove(parentIdentifier, activeConnection); release(user, connection); throw e; @@ -314,18 +269,7 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS @Override public List getActiveConnections(Connection connection) { - synchronized (activeConnections) { - - String identifier = connection.getIdentifier(); - - // Get set of active connection records - LinkedList connections = activeConnections.get(identifier); - if (connections != null) - return Collections.unmodifiableList(connections); - - return Collections.EMPTY_LIST; - - } + return activeConnections.get(connection.getIdentifier()); } @Override @@ -362,8 +306,13 @@ public abstract class AbstractGuacamoleSocketService implements GuacamoleSocketS @Override public List getActiveConnections(ConnectionGroup connectionGroup) { - // STUB - return Collections.EMPTY_LIST; + + // If not a balancing group, assume no connections + if (connectionGroup.getType() != ConnectionGroup.Type.BALANCING) + return Collections.EMPTY_LIST; + + return activeConnectionGroups.get(connectionGroup.getIdentifier()); + } } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/socket/ActiveConnectionMultimap.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/socket/ActiveConnectionMultimap.java new file mode 100644 index 000000000..5c6213965 --- /dev/null +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/glyptodon/guacamole/auth/jdbc/socket/ActiveConnectionMultimap.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2015 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 org.glyptodon.guacamole.auth.jdbc.socket; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import org.glyptodon.guacamole.net.auth.ConnectionRecord; + + +/** + * Mapping of object identifiers to lists of connection records. Records are + * added or removed individually, and the overall list of current records + * associated with a given object can be retrieved at any time. The public + * methods of this class are all threadsafe. + * + * @author Michael Jumper + */ +public class ActiveConnectionMultimap { + + /** + * All active connections to a connection having a given identifier. + */ + private final Map> records = + new HashMap>(); + + /** + * Stores the given connection record in the list of active connections + * associated with the object having the given identifier. + * + * @param identifier + * The identifier of the object being connected to. + * + * @param record + * The record associated with the active connection. + */ + public void put(String identifier, ConnectionRecord record) { + synchronized (records) { + + // Get set of active connection records, creating if necessary + LinkedList connections = records.get(identifier); + if (connections == null) { + connections = new LinkedList(); + records.put(identifier, connections); + } + + // Add active connection + connections.addFirst(record); + + } + } + + /** + * Removes the given connection record from the list of active connections + * associated with the object having the given identifier. + * + * @param identifier + * The identifier of the object being disconnected from. + * + * @param record + * The record associated with the active connection. + */ + public void remove(String identifier, ConnectionRecord record) { + synchronized (records) { + + // Get set of active connection records + LinkedList connections = records.get(identifier); + assert(connections != null); + + // Remove old record + connections.remove(record); + + // If now empty, clean the tracking entry + if (connections.isEmpty()) + records.remove(identifier); + + } + } + + /** + * Returns the current list of active connection records associated with + * the object having the given identifier. The list will be sorted in + * ascending order of connection age. If there are no such connections, an + * empty list is returned. + * + * @param identifier + * The identifier of the object to check. + * + * @return + * An immutable list of records associated with the object having the + * given identifier, or an empty list if there are no such records. + */ + public List get(String identifier) { + synchronized (records) { + + // Get set of active connection records + LinkedList connections = records.get(identifier); + if (connections != null) + return Collections.unmodifiableList(connections); + + return Collections.EMPTY_LIST; + + } + } + +}