mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
GUAC-1101: Implement reserved concurrency policy.
This commit is contained in:
@@ -24,9 +24,12 @@ package org.glyptodon.guacamole.auth.jdbc.socket;
|
||||
|
||||
import com.google.inject.Singleton;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import org.glyptodon.guacamole.auth.jdbc.user.AuthenticatedUser;
|
||||
import org.glyptodon.guacamole.auth.jdbc.connection.ModeledConnection;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.GuacamoleResourceConflictException;
|
||||
|
||||
|
||||
/**
|
||||
@@ -43,17 +46,138 @@ import org.glyptodon.guacamole.GuacamoleException;
|
||||
public class ReservedGuacamoleSocketService
|
||||
extends AbstractGuacamoleSocketService {
|
||||
|
||||
/**
|
||||
* An arbitrary number of reservations associated with a specific user.
|
||||
* Initially, each Reservation instance represents exactly one reservation,
|
||||
* but future calls to acquire() may increase this value. Once the
|
||||
* reservation count is reduced to zero by calls to release(), a
|
||||
* Reservation instance is empty and cannot be reused. It must be discarded
|
||||
* and replaced with a fresh Reservation.
|
||||
*
|
||||
* This is necessary as each Reservation will be stored within a Map, and
|
||||
* the effect of acquire() must be deterministic. If Reservations could be
|
||||
* reused, the internal count could potentially increase after being
|
||||
* removed from the map, resulting in a successful acquire() that really
|
||||
* should have failed.
|
||||
*/
|
||||
private static class Reservation {
|
||||
|
||||
/**
|
||||
* The username of the user associated with this reservation.
|
||||
*/
|
||||
private final String username;
|
||||
|
||||
/**
|
||||
* The number of reservations effectively present under the associated
|
||||
* username.
|
||||
*/
|
||||
private int count = 1;
|
||||
|
||||
/**
|
||||
* Creates a new reservation which tracks the overall number of
|
||||
* reservations for a given user.
|
||||
* @param username
|
||||
*/
|
||||
public Reservation(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to acquire a new reservation under the given username. If
|
||||
* this reservation is for a different user, or the reservation has
|
||||
* expired, this will fail.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user to acquire the reservation for.
|
||||
*
|
||||
* @return
|
||||
* true if the reservation was successful, false otherwise.
|
||||
*/
|
||||
public boolean acquire(String username) {
|
||||
|
||||
// Acquire always fails if for the wrong user
|
||||
if (!this.username.equals(username))
|
||||
return false;
|
||||
|
||||
// Determine success/failure based on count
|
||||
synchronized (this) {
|
||||
|
||||
// If already expired, no further reservations are allowed
|
||||
if (count == 0)
|
||||
return false;
|
||||
|
||||
// Otherwise, add another reservation, report success
|
||||
count++;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases a previous reservation. The result of calling this function
|
||||
* without a previous matching call to acquire is undefined.
|
||||
*
|
||||
* @return
|
||||
* true if the last reservation has been released and this
|
||||
* reservation is now empty, false otherwise.
|
||||
*/
|
||||
public boolean release() {
|
||||
synchronized (this) {
|
||||
|
||||
// Reduce reservation count
|
||||
count--;
|
||||
|
||||
// Empty if no reservations remain
|
||||
return count == 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of connection identifier to associated reservations.
|
||||
*/
|
||||
private final ConcurrentMap<String, Reservation> reservations =
|
||||
new ConcurrentHashMap<String, Reservation>();
|
||||
|
||||
@Override
|
||||
protected ModeledConnection acquire(AuthenticatedUser user,
|
||||
List<ModeledConnection> connections) throws GuacamoleException {
|
||||
// STUB
|
||||
throw new UnsupportedOperationException("STUB");
|
||||
|
||||
String username = user.getUser().getIdentifier();
|
||||
|
||||
// Return the first successfully-reserved connection
|
||||
for (ModeledConnection connection : connections) {
|
||||
|
||||
String identifier = connection.getIdentifier();
|
||||
|
||||
// Attempt to reserve connection, return if successful
|
||||
Reservation reservation = reservations.putIfAbsent(identifier, new Reservation(username));
|
||||
if (reservation == null || reservation.acquire(username))
|
||||
return connection;
|
||||
|
||||
}
|
||||
|
||||
// Already in use
|
||||
throw new GuacamoleResourceConflictException("Cannot connect. This connection is in use.");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void release(AuthenticatedUser user, ModeledConnection connection) {
|
||||
// STUB
|
||||
throw new UnsupportedOperationException("STUB");
|
||||
|
||||
String identifier = connection.getIdentifier();
|
||||
|
||||
// Retrieve active reservation (which must exist)
|
||||
Reservation reservation = reservations.get(identifier);
|
||||
assert(reservation != null);
|
||||
|
||||
// Release reservation, remove from map if empty
|
||||
if (reservation.release())
|
||||
reservations.remove(identifier);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user