ome.services.blitz.fire.SessionManagerI.java Source code

Java tutorial

Introduction

Here is the source code for ome.services.blitz.fire.SessionManagerI.java

Source

/*
 *   $Id$
 *
 *   Copyright 2007 Glencoe Software, Inc. All rights reserved.
 *   Use is subject to license terms supplied in LICENSE.txt
 */

package ome.services.blitz.fire;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

import ome.logic.HardWiredInterceptor;
import ome.security.SecuritySystem;
import ome.services.blitz.impl.ServiceFactoryI;
import ome.services.blitz.util.ConvertToBlitzExceptionMessage;
import ome.services.blitz.util.RegisterServantMessage;
import ome.services.blitz.util.UnregisterServantMessage;
import ome.services.messages.DestroySessionMessage;
import ome.services.sessions.SessionManager;
import ome.services.sessions.events.ChangeSecurityContextEvent;
import ome.services.util.Executor;
import ome.system.OmeroContext;
import ome.system.Principal;
import ome.system.Roles;
import ome.util.messages.MessageException;
import omero.ApiUsageException;
import omero.WrappedCreateSessionException;
import omero.api.ClientCallbackPrxHelper;
import omero.constants.EVENT;
import omero.constants.GROUP;
import omero.constants.topics.HEARTBEAT;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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 Glacier2.CannotCreateSessionException;
import Glacier2.StringSetPrx;

/**
 * Central login logic for all OMERO.blitz clients. It is required to create a
 * {@link Glacier2.Session} via the {@link Glacier2.SessionManager} in order to
 * get through the firewall. The {@link Glacier2.Session} (here a
 * {@link ServiceFactoryI} instance) also manages all servants created by the
 * client.
 * 
 * @author Josh Moore, josh at glencoesoftware.com
 * @since 3.0-Beta2
 */
public final class SessionManagerI extends Glacier2._SessionManagerDisp
        implements ApplicationContextAware, ApplicationListener {

    /**
     * "ome.security.basic.BasicSecurityWiring" <em>may</em> be replaced by
     * another value at compile time (see blitz/build.xml), but a default value
     * is necessary here fore testing.
     */
    private final static List<HardWiredInterceptor> CPTORS = HardWiredInterceptor
            .parse(new String[] { "ome.security.basic.BasicSecurityWiring" });

    private final static Log log = LogFactory.getLog(SessionManagerI.class);

    protected OmeroContext context;

    protected final Ice.ObjectAdapter adapter;

    protected final SecuritySystem securitySystem;

    protected final SessionManager sessionManager;

    protected final Executor executor;

    protected final Ring ring;

    protected final Registry registry;

    protected final TopicManager topicManager;

    protected final AtomicBoolean loaded = new AtomicBoolean(false);

    /**
     * An internal mapping to all {@link ServiceFactoryI} instances for a given
     * session since there is no method on {@link Ice.ObjectAdapter} to retrieve
     * all servants.
     */
    protected final Map<String, Set<String>> sessionToClientIds = new ConcurrentHashMap<String, Set<String>>();

    public SessionManagerI(Ring ring, Ice.ObjectAdapter adapter, SecuritySystem secSys,
            SessionManager sessionManager, Executor executor, TopicManager topicManager, Registry reg) {
        this.ring = ring;
        this.registry = reg;
        this.adapter = adapter;
        this.executor = executor;
        this.securitySystem = secSys;
        this.topicManager = topicManager;
        this.sessionManager = sessionManager;
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = (OmeroContext) applicationContext;
        HardWiredInterceptor.configure(CPTORS, context);
        loaded.set(true);
    }

    public Glacier2.SessionPrx create(String userId, Glacier2.SessionControlPrx control, Ice.Current current)
            throws CannotCreateSessionException {

        if (!loaded.get()) {
            WrappedCreateSessionException wrapped = new WrappedCreateSessionException();
            wrapped.backOff = 1000L;
            wrapped.concurrency = true;
            wrapped.reason = "Server not fully initialized";
            wrapped.type = "ApiUsageException";
            throw wrapped;
        }

        try {

            // First thing we do is guarantee that the client is giving us
            // the required information.
            ServiceFactoryI.clientId(current); // throws ApiUsageException

            // Before asking the ring, see if we already have the
            // session locally.
            boolean local = false;
            try {
                Object o = sessionManager.find(userId);
                local = (o != null);
                log.info("Found session locally: " + userId);
            } catch (Exception e) {
                log.debug("Exception while waiting on " + "SessionManager.find " + e);
            }

            // If not, then give the ring a chance to redirect to
            // other instances which may already have it.
            if (!local) {
                Glacier2.SessionPrx sf = ring.getProxyOrNull(userId, control, current);
                if (sf != null) {
                    return sf; // EARLY EXIT
                }
            }

            // Defaults
            Roles roles = securitySystem.getSecurityRoles();

            String group = getGroup(current);
            if (group == null) {
                group = roles.getUserGroupName();
            }
            String event = getEvent(current);
            if (event == null) {
                event = "User"; // FIXME This should be in Roles as well.
            }
            String agent = getAgent(current);

            // Create the session for this ServiceFactory
            Principal p = new Principal(userId, group, event);
            ome.model.meta.Session s = sessionManager.createWithAgent(p, agent);
            Principal sp = new Principal(s.getUuid(), group, event);
            // Event raised to add to Ring

            // Create the ServiceFactory
            ServiceFactoryI session = new ServiceFactoryI(local /* ticket:911 */, current, control, context,
                    sessionManager, executor, sp, CPTORS, topicManager, registry);

            Ice.Identity id = session.sessionId();

            if (control != null) {
                // Not having a control implies that this is an internal
                // call, not coming through Glacier, so we can trust it.
                StringSetPrx cat = control.categories();
                cat.add(new String[] { id.category });
                cat.add(new String[] { id.name });
            }

            Ice.ObjectPrx _prx = current.adapter.add(session, id); // OK Usage
            _prx = current.adapter.createDirectProxy(id);

            // Logging & sessionToClientIds addition
            if (!sessionToClientIds.containsKey(s.getUuid())) {
                sessionToClientIds.put(s.getUuid(), new HashSet<String>());
                log.info(String.format("Created session %s for user %s (agent=%s)", session, userId, agent));
            } else {
                if (log.isInfoEnabled()) {
                    log.info(String.format("Rejoining session %s (agent=%s)", session, agent));
                }
            }
            sessionToClientIds.get(s.getUuid()).add(session.clientId);
            return Glacier2.SessionPrxHelper.uncheckedCast(_prx);

        } catch (Exception t) {

            // Then we are good to go.
            if (t instanceof CannotCreateSessionException) {
                throw (CannotCreateSessionException) t;
            }

            // These need special handling as well.
            else if (t instanceof ome.conditions.ConcurrencyException || t instanceof omero.ConcurrencyException) {

                // Parse out the back off, then everything is generic.
                long backOff = (t instanceof omero.ConcurrencyException) ? ((omero.ConcurrencyException) t).backOff
                        : ((ome.conditions.ConcurrencyException) t).backOff;

                WrappedCreateSessionException wrapped = new WrappedCreateSessionException();
                wrapped.backOff = backOff;
                wrapped.type = t.getClass().getName();
                wrapped.concurrency = true;
                wrapped.reason = "ConcurrencyException: " + t.getMessage() + "\nPlease retry in " + backOff
                        + "ms. Cause: " + t.getMessage();
                throw wrapped;

            }

            ConvertToBlitzExceptionMessage convert = new ConvertToBlitzExceptionMessage(this, t);
            try {
                // TODO Possibly provide context.convert(ConversionMsg) methd.
                context.publishMessage(convert);
            } catch (Throwable t2) {
                log.error("Error while converting exception:", t2);
            }

            if (convert.to instanceof CannotCreateSessionException) {
                throw (CannotCreateSessionException) convert.to;
            }

            // We make an exception for some more or less "expected" exception
            // types. Everything else gets logged as an error which we need
            // to review.
            if (!(t instanceof omero.ApiUsageException || t instanceof ome.conditions.ApiUsageException
                    || t instanceof ome.conditions.SecurityViolation)) {
                log.error("Error while creating ServiceFactoryI", t);
            }

            WrappedCreateSessionException wrapped = new WrappedCreateSessionException();
            wrapped.backOff = -1;
            wrapped.concurrency = false;
            wrapped.reason = t.getMessage();
            wrapped.type = t.getClass().getName();
            wrapped.setStackTrace(t.getStackTrace());
            throw wrapped;
        }
    }

    // Listener
    // =========================================================================

    public void onApplicationEvent(ApplicationEvent event) {
        try {
            if (event instanceof UnregisterServantMessage) {
                UnregisterServantMessage msg = (UnregisterServantMessage) event;
                Ice.Current curr = msg.getCurrent();

                // And unregister the service if possible
                Ice.Identity id = getServiceFactoryIdentity(curr);
                ServiceFactoryI sf = getServiceFactory(id);
                if (sf != null) {
                    sf.unregisterServant(curr.id);
                }
            } else if (event instanceof RegisterServantMessage) {
                RegisterServantMessage msg = (RegisterServantMessage) event;
                Ice.Current curr = msg.getCurrent();
                Ice.Identity id = getServiceFactoryIdentity(curr);
                ServiceFactoryI sf = getServiceFactory(id);
                if (sf != null) {
                    Ice.Identity newId = new Ice.Identity(UUID.randomUUID().toString(), id.name);
                    msg.setProxy(sf.registerServant(newId, msg.getServant()));
                }
            } else if (event instanceof DestroySessionMessage) {
                DestroySessionMessage msg = (DestroySessionMessage) event;
                reapSession(msg.getSessionId());
            } else if (event instanceof ChangeSecurityContextEvent) {
                ChangeSecurityContextEvent csce = (ChangeSecurityContextEvent) event;
                checkStatefulServices(csce);
            }
        } catch (Throwable t) {
            throw new MessageException("SessionManagerI.onApplicationEvent", t);
        }
    }

    /**
     * Checks that there are no stateful services active for the session.
     */
    void checkStatefulServices(ChangeSecurityContextEvent csce) {
        String uuid = csce.getUuid();
        Set<String> clientIds = sessionToClientIds.get(uuid);
        if (clientIds == null) {
            return; // nothing to be done. should only happen during testing.
        }
        clientIds = new HashSet<String>(clientIds);
        for (String clientId : clientIds) {
            try {
                ServiceFactoryI sf = getServiceFactory(clientId, uuid);
                if (sf != null) {
                    String servants = sf.getStatefulServiceCount();
                    if (servants.length() > 0) {
                        csce.cancel("Client " + clientId + " has active stateful services:\n" + servants);
                    }
                }
            } catch (Exception e) {
                // ignore
            }
        }
    }

    /**
     * {@link ServiceFactoryI#doDestroy() Destroys} all the
     * {@link ServiceFactoryI} instances based on the given sessionId. Multiple
     * clients can be attached to the same session, each with its own
     * {@link ServiceFactoryI}
     */
    public void requestHeartBeats() {
        log.info("Performing requestHeartbeats");
        this.context.publishEvent(new TopicManager.TopicMessage(this, HEARTBEAT.value,
                new ClientCallbackPrxHelper(), "requestHeartbeat"));
    }

    /**
     * {@link ServiceFactoryI#doDestroy() Destroys} all the
     * {@link ServiceFactoryI} instances based on the given sessionId. Multiple
     * clients can be attached to the same session, each with its own
     * {@link ServiceFactoryI}
     */
    public void reapSession(String sessionId) {
        Set<String> clientIds = sessionToClientIds.get(sessionId);
        if (clientIds != null) {
            if (clientIds.size() > 0) {
                log.info("Reaping " + clientIds.size() + " clients for " + sessionId);
            }
            for (String clientId : clientIds) {
                try {
                    ServiceFactoryI sf = getServiceFactory(clientId, sessionId);
                    if (sf != null) {
                        sf.doDestroy();
                        Ice.Identity id = sf.sessionId();
                        log.info("Removing " + sf);
                        adapter.remove(id); // OK ADAPTER USAGE
                    }
                } catch (Ice.ObjectAdapterDeactivatedException oade) {
                    log.warn("Cannot reap session " + sessionId + " from client " + clientId
                            + " since adapter is deactivated.");
                } catch (Exception e) {
                    log.error("Error reaping session " + sessionId + " from client " + clientId, e);
                }
            }
        }
        sessionToClientIds.remove(sessionId);
    }

    // Helpers
    // =========================================================================

    protected ServiceFactoryI getServiceFactory(String clientId, String sessionId) {
        Ice.Identity iid = ServiceFactoryI.sessionId(clientId, sessionId);
        return getServiceFactory(iid);
    }

    protected ServiceFactoryI getServiceFactory(Ice.Identity iid) {
        Ice.Object obj = adapter.find(iid);
        if (obj == null) {
            log.debug(Ice.Util.identityToString(iid) + " already removed.");
            return null;
        }

        if (obj instanceof ServiceFactoryI) {
            ServiceFactoryI sf = (ServiceFactoryI) obj;
            return sf;
        } else {
            log.warn("Not a ServiceFactory: " + obj);
            return null;
        }

    }

    protected Ice.Identity getServiceFactoryIdentity(Ice.Current curr) {
        Ice.Identity id;
        try {
            String clientId = ServiceFactoryI.clientId(curr);
            id = ServiceFactoryI.sessionId(clientId, curr.id.category);
        } catch (ApiUsageException e) {
            throw new RuntimeException(
                    "Cannot create session id for servant:" + String.format("\nInfo:\n\tId:%s\n\tOp:%s\n\tCtx:%s",
                            Ice.Util.identityToString(curr.id), curr.operation, curr.ctx),
                    e);
        }
        return id;
    }

    protected String getGroup(Ice.Current current) {
        if (current.ctx == null) {
            return null;
        }
        return current.ctx.get(GROUP.value);
    }

    protected String getAgent(Ice.Current current) {
        if (current.ctx == null) {
            return null;
        }
        return current.ctx.get("omero.agent");
    }

    protected String getEvent(Ice.Current current) {
        if (current.ctx == null) {
            return null;
        }
        return current.ctx.get(EVENT.value);
    }

}