gt:                 return executeSessionContextLookup(sf, p, ctx.getSession());
context: /*
   *   $Id$
   *
   *   Copyright 2007 Glencoe Software, Inc. All rights reserved.
   *   Use is subject to license terms supplied in LICENSE.txt
   */
  package ome.services.sessions;
  
  import java.sql.Timestamp;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.UUID;
  import java.util.concurrent.Callable;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.concurrent.Future;
  
  import net.sf.ehcache.Ehcache;
  import net.sf.ehcache.Element;
  import ome.api.local.LocalAdmin;
  import ome.api.local.LocalQuery;
  import ome.api.local.LocalUpdate;
  import ome.conditions.ApiUsageException;
  import ome.conditions.AuthenticationException;
  import ome.conditions.InternalException;
  import ome.conditions.RemovedSessionException;
  import ome.conditions.SecurityViolation;
  import ome.conditions.SessionException;
  import ome.conditions.SessionTimeoutException;
  import ome.model.enums.EventType;
  import ome.model.internal.Details;
  import ome.model.internal.Permissions;
  import ome.model.meta.Experimenter;
  import ome.model.meta.ExperimenterGroup;
  import ome.model.meta.Node;
  import ome.model.meta.Session;
  import ome.model.meta.Share;
  import ome.parameters.Filter;
  import ome.parameters.Parameters;
  import ome.security.basic.PrincipalHolder;
  import ome.services.messages.CreateSessionMessage;
  import ome.services.messages.DestroySessionMessage;
  import ome.services.sessions.events.ChangeSecurityContextEvent;
  import ome.services.sessions.events.UserGroupUpdateEvent;
  import ome.services.sessions.state.SessionCache;
  import ome.services.sessions.stats.CounterFactory;
  import ome.services.sessions.stats.SessionStats;
  import ome.services.util.Executor;
  import ome.services.util.Executor.Priority;
  import ome.system.EventContext;
  import ome.system.OmeroContext;
  import ome.system.Principal;
  import ome.system.Roles;
  import ome.system.ServiceFactory;
  import ome.util.SqlAction;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.beans.BeansException;
  import org.springframework.context.ApplicationContext;
  import org.springframework.context.ApplicationContextAware;
  import org.springframework.context.ApplicationEvent;
  import org.springframework.context.ApplicationListener;
  import org.springframework.dao.DataAccessException;
  import org.springframework.dao.EmptyResultDataAccessException;
  import org.springframework.transaction.annotation.Transactional;
  
  /**
   * Is for ISession a cache and will be kept there in sync? OR Factors out the
   * logic from ISession and SessionManagerI
   * 
   * Therefore either called directly, or via synchronous messages.
   * 
   * Uses the name of a Principal as the key to the session. We may need to limit
   * user names to prevent this. (Strictly alphanumeric)
   *
   * Receives notifications as an {@link ApplicationListener}, which should be
   * used to keep the {@link Session} instances up-to-date.
   *
   * @author Josh Moore, josh at glencoesoftware.com
   * @since 3.0-Beta3
   */
  public class SessionManagerImpl implements SessionManager, SessionCache.StaleCacheListener,
          ApplicationContextAware, ApplicationListener {
  
      private final static Logger log = LoggerFactory.getLogger(SessionManagerImpl.class);
  
      /**
       * The id of this session manager, used to identify its own actions. This
       * value may be overwritten by an injector with a value which is used
       * throughout this server instance.
       */
      private String internal_uuid = UUID.randomUUID().toString();
  
      // Injected
      protected OmeroContext context;
      protected Roles roles;
      protected SessionCache cache;
      protected Executor executor;
      protected long defaultTimeToIdle;
      protected long maxUserTimeToIdle;
      protected long defaultTimeToLive;
      protected long maxUserTimeToLive;
      protected PrincipalHolder principalHolder;
      protected CounterFactory factory;
      protected boolean readOnly = false;
  
      // Local state
  
      /**
       * A private session for use only by this instance for running methods via
       * {@link Executor}. The name of this {@link Principal} will not be removed
       * by calls to {@link #closeAll()}.
       */
      protected Principal asroot;
  
      /**
       * Internal {@link SessionContext} created during {@link #init()} which is
       * used for all method calls internal to the session manager (see execute*
       * methods)
       */
      protected SessionContext internalSession;
  
      // ~ Injectors
      // =========================================================================
  
      public void setApplicationContext(ApplicationContext applicationContext)
              throws BeansException {
          this.context = (OmeroContext) applicationContext;
      }
  
      public void setUuid(String uuid) {
          this.internal_uuid = uuid;
      }
  
      public void setSessionCache(SessionCache sessionCache) {
          cache = sessionCache;
          this.cache.setStaleCacheListener(this);
      }
  
      public void setRoles(Roles securityRoles) {
          roles = securityRoles;
      }
  
      public void setExecutor(Executor executor) {
          this.executor = executor;
      }
  
      public void setDefaultTimeToIdle(long defaultTimeToIdle) {
          this.defaultTimeToIdle = defaultTimeToIdle;
          this.maxUserTimeToIdle = Math.min(Long.MAX_VALUE / 10,
                  defaultTimeToIdle);
          this.maxUserTimeToIdle *= 10;
      }
  
      public void setDefaultTimeToLive(long defaultTimeToLive) {
          this.defaultTimeToLive = defaultTimeToLive;
          this.maxUserTimeToLive = Math.min(Long.MAX_VALUE / 10,
                  defaultTimeToLive);
          this.maxUserTimeToLive *= 10;
  
      }
  
      public void setPrincipalHolder(PrincipalHolder principal) {
          this.principalHolder = principal;
      }
  
      public void setCounterFactory(CounterFactory factory) {
          this.factory = factory;
      }
  
      public void setReadOnly(boolean readOnly) {
          this.readOnly = readOnly;
      }
  
      /**
       * Initialization method called by the Spring run-time to acquire an initial
       * {@link Session}.
       */
      public void init() {
          try {
              asroot = new Principal(internal_uuid, "system", "Sessions");
              final Session session = executeInternalSession();
              internalSession = new InternalSessionContext(session, roles);
              cache.putSession(internal_uuid, internalSession);
          } catch (DataAccessException dataAccess) {
              throw new RuntimeException(
                      "          "
                              + "=====================================================\n"
                              + "Data access exception: Did you create your database? \n"
                              + "=====================================================\n",
                      dataAccess);
          }
      }
  
      // ~ Session definition
      // =========================================================================
  
      protected void define(Session s, String uuid, String message, long started,
              long idle, long live, String eventType, String agent) {
  
          s.getDetails().setPermissions(Permissions.PRIVATE);
          s.setUuid(uuid);
          s.setMessage(message);
          s.setStarted(new Timestamp(started));
          s.setTimeToIdle(idle);
          s.setTimeToLive(live);
          s.setDefaultEventType(eventType);
          s.setUserAgent(agent);
      }
  
      // ~ Session management
      // =========================================================================
  
      /*
       * Is given trustable values by the {@link SessionBean}
       */
      public Session createWithAgent(final Principal _principal, final String credentials, String agent) {
  
          // If credentials exist as session, then return that
          try {
              SessionContext context = cache
                      .getSessionContext(credentials);
              if (context != null) {
                  context.count().increment();
                  return context.getSession(); // EARLY EXIT!
              }
          } catch (SessionException se) {
              // oh well.
          }
  
          // Though trusted values, if we receive a null principal, not ok;
          boolean ok = _principal == null ? false : executeCheckPassword(
                  _principal, credentials);
  
          if (!ok) {
              log.warn("Failed to authenticate: " + _principal);
              throw new AuthenticationException("Authentication exception.");
          }
  
          // authentication checked. Now delegating to the admin method (no pass)
          return createWithAgent(_principal, agent);
      }
  
      public Session createWithAgent(Principal principal, String agent) {
          Session session = new Session();
          define(session, UUID.randomUUID().toString(), "Initial message.",
                  System.currentTimeMillis(), defaultTimeToIdle,
                  defaultTimeToLive, principal.getEventType(), agent);
          return createSession(principal, session);
      }
  
      public Share createShare(Principal principal, boolean enabled,
              long timeToLive, String eventType, String description,
              long groupId) {
          Share share = newShare();
          define(share, UUID.randomUUID().toString(), description, System
                  .currentTimeMillis(), defaultTimeToIdle, timeToLive, eventType,
                  "Share");
          share.setGroup(new ExperimenterGroup(groupId, false));
          share.setActive(enabled);
          share.setData(new byte[] {});
          share.setItemCount(0L);
          return (Share) createSession(principal, share);
      }
  
      @SuppressWarnings("unchecked")
      private Session createSession(final Principal principal,
              final Session oldsession) {
          // If username exists as session, then return that
          try {
              SessionContext context = cache.getSessionContext(principal.getName());
              if (context != null) {
                  context.count().increment();
                  return context.getSession(); // EARLY EXIT!
              }
          } catch (SessionException se) {
              // oh well
          }
  
          List rv;
          if (readOnly) {
              rv = (List) executor.execute(this.asroot, new Executor.SimpleWork(
                      this, "read-only createSession") {
                  @Transactional(readOnly = true)
                  public Object doWork(org.hibernate.Session __s,
                          ServiceFactory sf) {
                      Principal p = checkPrincipalNameAndDefaultGroup(sf,
                              principal);
                      long userId = executeLookupUser(sf, p);
                      // Not performed! Session s = executeUpdate(sf, oldsession,
                      // userId);
                      Session s = oldsession;
                      return executeSessionContextLookup(sf, p, s);
                  }
              });
          } else {
              rv = (List) executor.execute(this.asroot, new Executor.SimpleWork(
                      this, "createSession") {
                  @Transactional(readOnly = false)
                  public Object doWork(org.hibernate.Session __s,
                          ServiceFactory sf) {
                      Principal p = checkPrincipalNameAndDefaultGroup(sf,
                              principal);
                      long userId = executeLookupUser(sf, p);
                      Session s = executeUpdate(sf, oldsession, userId);
                      return executeSessionContextLookup(sf, p, s);
                  }
  
              });
          }
  
          if (rv == null) {
              throw new RemovedSessionException("No info in database for "
                      + principal);
          }
          SessionContext newctx = createSessionContext(rv, null);
  
          // This the publishEvent returns successfully, then we will have to
          // handle rolling back this addition our selves
          String uuid = newctx.getCurrentSessionUuid();
          cache.putSession(uuid, newctx);
          try {
              context.publishEvent(new CreateSessionMessage(this, uuid));
          } catch (RuntimeException re) {
              log.warn("Session creation cancelled by event listener", re);
              cache.removeSession(uuid);
              throw re;
          }
  
          // All successful, increment and return.
          newctx.count().increment();
          return newctx.getSession();
      }
  
      public Session update(Session session) {
          return update(session, false);
      }
  
      public Session update(final Session session, final boolean trusted) {
  
          if (session == null || !session.isLoaded() || session.getUuid() == null) {
              throw new RemovedSessionException("Cannot update; No uuid.");
          }
  
          final String uuid = session.getUuid();
          final Details details = session.getDetails();
          final SessionContext ctx = cache.getSessionContext(uuid);
          if (ctx == null) {
              throw new RemovedSessionException(
                      "Can't update; No session with uuid:" + uuid);
          }
  
          final Session orig = ctx.getSession();
          
          // TODO // FIXME
          // =====================================================
          // This needs to get smarter
  
          List list = (List) executor.execute(asroot, new Executor.SimpleWork(
                  this, "load_for_update") {
              @Transactional(readOnly = false)
              public Object doWork(org.hibernate.Session __s, ServiceFactory sf) {
  
                  // Allow user to change default group
                  String defaultGroup = null;
                  if (details != null) {
                      ExperimenterGroup group = details.getGroup();
                      if (group != null) {
                          try {
                              Long groupId = group.getId();
                              if (groupId != null) {
                                  group = ((LocalAdmin) sf.getAdminService())
                                          .groupProxy(groupId);
                                  if (group != null) {
                                      defaultGroup = group.getName();
                                  }
                              }
                          } catch (Exception e) {
                              throw new ApiUsageException(
                                      "Cannot change default group to " + group
                                              + "\n" + e.getMessage());
                          }
                      }
                  }
  
                  // If still null, take the current
                  if (defaultGroup == null) {
                      defaultGroup = ctx.getCurrentGroupName();
                  }
  
                  Principal principal = new Principal(ctx.getCurrentUserName(),
                          defaultGroup, ctx.getCurrentEventType());
                  principal = checkPrincipalNameAndDefaultGroup(sf, principal);
  
                  // Unconditionally settable; these are open to the user for
                  // change
                  parseAndSetDefaultType(session.getDefaultEventType(), orig);
                  parseAndSetUserAgent(session.getUserAgent(), orig);
  
                  // Conditionally settable
                  parseAndSetTimeouts(session.getTimeToLive(), session
                          .getTimeToIdle(), orig, trusted);
  
                  // TODO Need to handle notifications
                  return executeSessionContextLookup(sf, principal, orig);
  
              }
          });
  
          if (list == null) {
              log.info("removeSession on update: " + uuid);
              cache.removeSession(uuid);
              throw new RemovedSessionException("Database contains no info for "
                      + uuid);
          }
          ;
  
          final SessionContext newctx = createSessionContext(list, ctx);
          final Session copy = copy(orig);
          executor.execute(asroot, new Executor.SimpleWork(this, "update") {
              @Transactional(readOnly = false)
              public Object doWork(org.hibernate.Session __s, ServiceFactory sf) {
                  return executeUpdate(sf, copy, newctx.getCurrentUserId());
              }
          });
          cache.putSession(uuid, newctx);
          return copy(orig);
  
      }
  
      /**
       * Takes a snapshot as from
       * {@link #executeSessionContextLookup(ServiceFactory, Principal)} and turns
       * it into a {@link SessionContext} instance. List argument should never be
       * null. Abort if
       * {@link #executeSessionContextLookup(ServiceFactory, Principal)} returns
       * null.
       */
      @SuppressWarnings("unchecked")
      protected SessionContext createSessionContext(List<?> list, SessionContext previous) {
  
          final Experimenter exp = (Experimenter) list.get(0);
          final ExperimenterGroup grp = (ExperimenterGroup) list.get(1);
          final List<Long> memberOfGroupsIds = (List<Long>) list.get(2);
          final List<Long> leaderOfGroupsIds = (List<Long>) list.get(3);
          final List<String> userRoles = (List<String>) list.get(4);
          final Principal principal = (Principal) list.get(5);
          final Session session = (Session) list.get(6);
  
          parseAndSetDefaultType(principal.getEventType(), session);
  
          session.getDetails().setOwner(exp);
          session.getDetails().setGroup(grp);
  
          SessionContext sessionContext = new SessionContextImpl(session,
                  leaderOfGroupsIds, memberOfGroupsIds, userRoles, factory
                          .createStats(), roles, previous);
          return sessionContext;
      }
  
      public Session find(String uuid) {
          SessionContext sessionContext = cache.getSessionContext(uuid);
          return (sessionContext == null) ? null : sessionContext.getSession();
      }
  
      private final static String findBy1 =
          "select s.id, s.uuid from Session s " +
          "join s.owner o where " +
          "s.closed is null and o.omeName = :name ";
  
      private final static String findByOrder =
          "order by s.started desc";
  
      private List<Session> findByQuery(String query, Parameters p) {
          List<Object[]> ids_uuids = executeProjection(query, p);
          List<Session> rv = new ArrayList<Session>();
          for (Object[] arr : ids_uuids) {
              String uuid = (String) arr[1];
              try {
                  SessionContext sc = cache.getSessionContext(uuid);
                  rv.add(sc.getSession());
              } catch (Exception e) {
                  // skip
              }
          }
          return rv;
      }
  
      public List<Session> findByUser(String user) {
          return findByQuery(findBy1 + findByOrder,
                  new Parameters().addString("name", user));
      }
  
      public List<Session> findByUserAndAgent(String user, String... agents) {
          StringBuilder sb = new StringBuilder();
          sb.append(findBy1);
          Parameters p = new Parameters().addString("name", user);
          if (agents == null || agents.length == 0 ||
                  (agents.length == 1  && agents[0] == null)) {
              sb.append("and s.userAgent is null ");
          } else {
              sb.append("and s.userAgent in (:agents) ");
              p.addList("agents", Arrays.asList(agents));
          }
          sb.append(findByOrder);
          return findByQuery(sb.toString(), p);
      }
  
      public int getReferenceCount(String uuid) {
          SessionContext ctx = cache.getSessionContext(uuid);
          return ctx.count().get();
      }
  
      public int detach(String uuid) {
          SessionContext ctx = cache.getSessionContext(uuid);
          return ctx.count().decrement();
      }
  
      public SessionStats getSessionStats(String uuid) {
          SessionContext ctx = cache.getSessionContext(uuid);
          return ctx.stats();
      }
  
      /*
       */
      public int close(String uuid) {
          SessionContext ctx;
          try {
              ctx = cache.getSessionContext(uuid);
          } catch (SessionException se) {
              log.info("closeSession called but doesn't exist: " + uuid);
              return -1; // EARLY EXIT!
          }
  
          int refCount = ctx.count().decrement();
          if (refCount < 1) {
              log.info("closeSession called and no more references: " + uuid);
              cache.removeSession(uuid);
              return -2;
          } else {
              log.info("closeSession called but " + refCount
                      + " more references: " + uuid);
              return refCount;
          }
      }
  
      public int closeAll() {
          Collection<String> ids = cache.getIds();
          for (String id : ids) {
              if (asroot.getName().equals(id)) {
                  continue; // DON'T KILL OUR ROOT SESSION
              }
              try {
                  log.info("closeAll called for " + id);
                  cache.removeSession(id);
              } catch (RemovedSessionException rse) {
                  // Ok. Done for us
              } catch (SessionTimeoutException ste) {
                  // Also ok
              } catch (Exception e) {
                  log.warn(String.format("Exception thrown on closeAll: %s:%s", e
                          .getClass().getName(), e.getMessage()));
              }
          }
          return ids.size();
      }
  
      public List<String> getUserRoles(String uuid) {
          SessionContext ctx = cache.getSessionContext(uuid);
          if (ctx == null) {
              throw new RemovedSessionException("No session with uuid: " + uuid);
          }
          return ctx.getUserRoles();
      }
  
      // ~ State attached to session
      // =========================================================================
  
      public Ehcache inMemoryCache(String uuid) {
          return cache.inMemoryCache(uuid);
      }
  
      public Ehcache onDiskCache(String uuid) {
          return cache.onDiskCache(uuid);
      }
  
      static String INPUT_ENVIRONMENT = "InputEnvironment";
      static String OUTPUT_ENVIRONMENT = "OutputEnvironment";
  
      public Object getInput(String session, String key)
              throws RemovedSessionException {
          return getEnvironmentVariable(session, key, INPUT_ENVIRONMENT);
      }
  
      public Object getOutput(String session, String key)
              throws RemovedSessionException {
          return getEnvironmentVariable(session, key, OUTPUT_ENVIRONMENT);
      }
  
      public Map<String, Object> inputEnvironment(String session) {
          return environment(session, INPUT_ENVIRONMENT);
      }
  
      public Map<String, Object> outputEnvironment(String session) {
          return environment(session, OUTPUT_ENVIRONMENT);
      }
  
      protected Map<String, Object> environment(String session, String env) {
  
          // Bump the last accessed time.
          // May throw a session exception
          getReferenceCount(session);
  
          Map<String, Object> rv = new HashMap<String, Object>();
          Element elt = inMemoryCache(session).get(env);
          if (elt == null) {
              return rv;
          }
          Map<String, Object> cv = (Map<String, Object>) elt.getObjectValue();
          if (cv == null) {
              return rv;
          }
  
          rv.putAll(cv);
          return rv;
      }
  
      public void setInput(String session, String key, Object object)
              throws RemovedSessionException {
          setEnvironmentVariable(session, key, object, INPUT_ENVIRONMENT);
      }
  
      public void setOutput(String session, String key, Object object)
              throws RemovedSessionException {
          setEnvironmentVariable(session, key, object, OUTPUT_ENVIRONMENT);
      }
  
      private Object getEnvironmentVariable(String session, String key, String env) {
          Ehcache cache = inMemoryCache(session);
          Element elt = cache.get(env);
          if (elt == null) {
              return null;
          }
  
          Map<String, Object> map = (Map<String, Object>) elt.getObjectValue();
          if (map == null) {
              return null;
          } else {
              return map.get(key);
          }
      }
  
      private void setEnvironmentVariable(String session, String key,
              Object object, String env) {
          Ehcache cache = inMemoryCache(session);
          Element elt = cache.get(env);
          Map<String, Object> map;
          if (elt == null) {
              map = new ConcurrentHashMap<String, Object>();
              elt = new Element(env, map);
              cache.put(elt);
          } else {
              map = (Map<String, Object>) elt.getObjectValue();
          }
          if (object == null) {
              map.remove(key);
          } else {
              map.put(key, object);
          }
      }
  
      // ~ Security methods
      // =========================================================================
  
      public EventContext getEventContext(Principal principal) {
          final SessionContext ctx = cache.getSessionContext(principal.getName());
          if (ctx == null) {
              throw new RemovedSessionException("No session with uuid:"
                      + principal.getName());
          }
          return ctx;
      }
  
      public EventContext reload(final String uuid) {
          final SessionContext ctx = cache.getSessionContext(uuid);
          if (ctx == null) {
              throw new RemovedSessionException("No session with uuid:"
                      + uuid);
          }
          Future<Object> future = executor.submit(Priority.SYSTEM,
                  new Callable<Object>(){
                      public Object call() throws Exception {
                          cache.reload(uuid);
                          return null;
                      }});
  
          // A freshly loaded session should now have been saved
          // as if it had been reloaded during synchronization.
          executor.get(future);
          return cache.getSessionContext(uuid);
      }
  
      // ~ Notifications
      // =========================================================================
  
      public String[] notifications(String sessionId) {
          return null;
      }
  
      /**
       */
      public void onApplicationEvent(ApplicationEvent event) {
          if (event instanceof UserGroupUpdateEvent) {
              cache.updateEvent((UserGroupUpdateEvent) event);
          } else if (event instanceof DestroySessionMessage) {
              executeCloseSession(((DestroySessionMessage) event).getSessionId());
          }
      }
  
      // ~ Callbacks (Registering session-based components)
      // =========================================================================
  
      public void addCallback(String sessionId, SessionCallback cb) {
  
      }
  
      public Object getCallbackObject(String sessionId, String name) {
          return null;
      }
  
      // ~ Misc
      // =========================================================================
  
      /**
       * Checks the validity of the given {@link Principal}, and in the case of an
       * error attempts to correct the problem by returning a new Principal.
       */
      private Principal checkPrincipalNameAndDefaultGroup(ServiceFactory sf,
              Principal p) {
  
          if (p == null || p.getName() == null) {
              throw new ApiUsageException("Null principal name.");
          }
  
          String type = p.getEventType();
          if (type == null) {
              type = "User";
          }
  
          // Null or bad event type values as well as umasks are handled
          // within the SessionManager and EventHandler. It is necessary
          String group = p.getGroup();
          if (group == null) {
              group = "user";
          }
  
          // ticket:404 -- preventing users from logging into "user" group
          else if (roles.getUserGroupName().equals(p.getGroup())) {
              // Throws an exception if no properly defined default group
              ExperimenterGroup g = _getDefaultGroup(sf, p.getName());
              if (g == null) {
                  throw new ApiUsageException("Can't find default group for "
                          + p.getName());
              }
              group = g.getName();
          }
  
          // Also checking event type. Throws if missing (and at least a NPE)
          type = sf.getTypesService().getEnumeration(EventType.class, type)
                  .getValue();
  
          Principal copy = new Principal(p.getName(), group, type);
          return copy;
      }
  
      private void parseAndSetDefaultType(String type, Session session) {
          String _type = (type == null) ? "User" : type;
          session.setDefaultEventType(_type);
      }
  
      /**
       * For the moment, user agent is nullable meaning that the only way to unset
       * a set value is by passing in null, so this is allowed here. This implies
       * that the best way to keep userAgent from being set to null is to always
       * return to ISession.updateSession() a session value which was originally
       * retrieved.
       */
      private void parseAndSetUserAgent(String userAgent, Session session) {
          session.setUserAgent(userAgent);
      }
  
      private void parseAndSetTimeouts(Long timeToLive, Long timeToIdle,
              Session session, boolean trusted) {
  
          if (timeToLive != null) {
  
              if (trusted) {
                  session.setTimeToLive(timeToLive);
              } else {
  
                  // Let users set a value within reasons
                  long activeTTL = Math.min(maxUserTimeToLive, timeToLive);
  
                  // But if the value is 0, then the default must also be 0
                  if (activeTTL == 0 && defaultTimeToLive != 0) {
                      throw new SecurityViolation("Cannot disable timeToLive. "
                              + "Value must be between 1 and "
                              + maxUserTimeToLive);
                  }
                  session.setTimeToLive(activeTTL);
              }
          }
  
          // As above
          if (timeToIdle != null) {
              if (trusted) {
                  session.setTimeToIdle(timeToIdle);
              } else {
                  long activeTTI = Math.min(maxUserTimeToIdle, timeToIdle);
                  if (activeTTI == 0 && defaultTimeToIdle != 0) {
                      throw new SecurityViolation("Cannot disable timeToIdle. "
                              + "Value must be between 1 and "
                              + maxUserTimeToIdle);
                  }
                  session.setTimeToIdle(activeTTI);
              }
          }
      }
  
      public Session copy(Session source) {
          if (source == null) {
              throw new ApiUsageException("Source may not be null.");
          }
  
          Session target;
          if (source instanceof Share) {
              target = newShare();
          } else {
              target = new Session();
          }
  
          target.setId(source.getId());
          target.setClosed(source.getClosed());
          target.setDefaultEventType(source.getDefaultEventType());
          target.getDetails().shallowCopy(source.getDetails());
          target.setMessage(source.getMessage());
          target.setNode(source.getNode());
          target.setStarted(source.getStarted());
          target.setTimeToIdle(source.getTimeToIdle());
          target.setTimeToLive(source.getTimeToLive());
          target.setUserAgent(source.getUserAgent());
          target.setUuid(source.getUuid());
  
          if (target instanceof Share) {
              Share to = (Share) target;
              Share from = (Share) source;
              to.setItemCount(from.getItemCount());
              to.setActive(from.getActive());
              to.setGroup(from.getGroup());
              to.setData(from.getData());
          }
  
          return target;
      }
  
      // StaleCacheListener
      // =========================================================================
  
      /**
       */
      public void prepareReload() {
          // Noop
      }
  
      /**
       * Will be called in a synchronized block by {@link SessionCache} in order
       * to allow for an update.
       */
      @SuppressWarnings({ "unchecked", "rawtypes" })
      public SessionContext reload(final SessionContext ctx) {
          final Principal p = new Principal(ctx.getCurrentUserName(), ctx
                  .getCurrentGroupName(), ctx.getCurrentEventType());
          List list = (List) executor.execute(asroot, new Executor.SimpleWork(
                  this, "reload", ctx.getSession().getUuid()) {
              @Transactional(readOnly = true)
              public Object doWork(org.hibernate.Session session,
                      ServiceFactory sf) {
  