/*
* $Id$
*
* Copyright 2008 Glencoe Software, Inc. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*
*/
package omero.util;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import omero.api._StatefulServiceInterfaceOperations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.MapMaker;
/**
* Manager for all active servants in a single session.
*
* To reduce the need of using {@link Ice.Util#stringToIdentity(String)} and
* {@link Ice.Util#identityToString(Ice.Identity)} the servant tries to make the
* two usages equivalent.
*
* @author Josh Moore, josh at glencoesoftware.com
* @since 3.0-Beta4
*/
public class ServantHolder {
private final static Logger log = LoggerFactory.getLogger(ServantHolder.class);
/**
* Note: servants are stored by String since {@link Ice.Identity} does not
* behave properly as a key.
*/
private final Map<String, Ice.Object> servants;
/**
* A lock cache that returns a {@link Lock} for each given name, constructing it only if necessary.
*/
private final LoadingCache<String, Lock> locks = CacheBuilder.newBuilder().build(
new CacheLoader<String, Lock>() {
@Override
public Lock load(String key) {
return new ReentrantLock();
}
});
/**
* An internal mapping to all client ids from {@link omero.cmd.SessionI} for a given
* DB session since there is no method on {@link Ice.ObjectAdapter} to retrieve
* all servants.
*/
protected final Map<String, Object> clientIds;
/**
* Storing session for debugging purposes.
*/
private final String session;
/**
* The number of servants that are allowed to be registered for a user
* in a single session before a {@link omero.OverUsageException} is thrown.
*/
private final int servantsPerSession;
public ServantHolder(String session) {
this(session, 10000);
}
public ServantHolder(String session, int servantsPerSession) {
final MapMaker mapMaker = new MapMaker();
this.servants = mapMaker.makeMap();
this.clientIds = mapMaker.makeMap();
this.session = session;
this.servantsPerSession = servantsPerSession;
}
//
// Session id related methods
//
public String getSession() {
return this.session;
}
/**
* Constructs an {@link Ice.Identity} from the current session
* and from the given {@link String} which for
* stateful services are defined by UUIDs.
*/
public Ice.Identity getIdentity(String idName) {
Ice.Identity id = new Ice.Identity();
id.category = this.session;
id.name = idName;
return id;
}
//
// ClientId methods
//
public void addClientId(String clientId) {
clientIds.put(clientId, Boolean.TRUE);
}
public void removeClientId(String clientId) {
clientIds.remove(clientId);
}
public Set<String> getClientIds() {
return clientIds.keySet();
}
/**
* Acquires the given lock or if necessary creates a new one.
* @param key the lock's key
*/
public void acquireLock(String key) {
try {
locks.get(key).lock();
} catch (ExecutionException e) {
/* cannot occur unless loading thread is interrupted */
}
}
/**
* Releases the given lock if found, otherwise throws an
* {@link ome.conditions.InternalException}
* @param key the lock's key
*/
public void releaseLock(String key) {
final Lock lock = locks.getIfPresent(key);
if (lock == null) {
throw new ome.conditions.InternalException("No lock found: " + key);
}
lock.unlock();
}
public Ice.Object get(Ice.Identity id) {
return get(id.name);
}
public Object getUntied(Ice.Identity id) {
Ice.Object servantOrTie = get(id.name);
if (servantOrTie instanceof Ice.TieBase) {
return ((Ice.TieBase) servantOrTie).ice_delegate();
} else {
return servantOrTie;
}
}
public void put(Ice.Identity id, Ice.Object servant)
throws omero.OverUsageException {
final int size = servants.size();
if (size >= servantsPerSession) {
String msg = String.format("servantsPerSession reached for %s: %s",
session, servantsPerSession);
log.error(msg);
omero.OverUsageException oue = new omero.OverUsageException();
omero.util.IceMapper.fillServerError(oue,
new ome.conditions.OverUsageException(msg));
throw oue;
}
double percent = (100.0 * size / servantsPerSession);
if (percent > 0 && (percent % 10) == 0) {
log.warn(String.format("%s of servants used for session %s",
(int) percent, session));
}
put(id.name, servant);
}
public Ice.Object remove(Ice.Identity id) {
return remove(id.name);
}
public List<String> getServantList() {
return new ArrayList<String>(servants.keySet());
}
public String getStatefulServiceCount() {
String list = "";
final List<String> servants = getServantList();
for (final String idName : servants) {
final Ice.Identity id = getIdentity(idName);
final Object servant = getUntied(id);
if (servant != null) {
try {
if (servant instanceof _StatefulServiceInterfaceOperations) {
list += "\n" + idName;
}
} catch (Exception e) {
// oh well
}
}
}
return list;
}
//
// Implementation
//
private void put(String key, Ice.Object servant) {
Object old = servants.put(key, servant);
if (old == null) {
log.debug(String.format("Added %s to %s as %s", servant, this, key));
} else {
log.debug(String.format("Replaced %s with %s to %s as %s", old, servant, this, key));
}
}
private Ice.Object remove(String key) {
Ice.Object servant = servants.remove(key);
log.debug(String.format("Removed %s from %s as %s", servant, this, key));
return servant;
}
private Ice.Object get(String key) {
return servants.get(key);
}
}