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

Java tutorial

Introduction

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

Source

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

package ome.services.blitz.fire;

import java.sql.Timestamp;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import ome.model.meta.Node;
import ome.services.blitz.redirect.NullRedirector;
import ome.services.blitz.redirect.Redirector;
import ome.services.blitz.util.BlitzConfiguration;
import ome.services.sessions.SessionManager;
import ome.services.util.Executor;
import ome.system.Principal;
import ome.system.ServiceFactory;
import ome.util.SqlAction;
import omero.grid.ClusterNodePrx;
import omero.grid.ClusterNodePrxHelper;
import omero.grid._ClusterNodeDisp;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.transaction.annotation.Transactional;

import Glacier2.CannotCreateSessionException;
import Glacier2.SessionPrx;
import Ice.Current;

/**
 * Distributed ring of {@link BlitzConfiguration} objects which manages lookups
 * of sessions and other resources from all the blitzes which take part in the
 * cluster. Membership in the {@link Ring} is based on a single token --
 * "omero.instance" -- retrieved from the current context, or if missing, a
 * calculated value which will prevent this instance from taking part in
 * clustering.
 * 
 * The {@link Ring} also listens for
 * 
 *@since Beta4
 */
public class Ring extends _ClusterNodeDisp implements Redirector.Context {

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

    /**
     * UUID for this cluster node. Used to uniquely identify the session manager
     * in this blitz instance. Most likely used in common with internal server
     * components. <em>Must</em> specify a valid session id.
     */
    public final String uuid;

    public final Principal principal;

    private final Executor executor;

    private final Redirector redirector;

    private/* final */Ice.Communicator communicator;

    private/* final */Registry registry;

    /**
     * Standard blitz adapter which is used for the callback.
     */
    private/* final */Ice.ObjectAdapter adapter;

    /**
     * Direct proxy value to the {@link SessionManager} in this blitz instance.
     */
    private/* final */String directProxy;

    public Ring(String uuid, Executor executor) {
        this(uuid, executor, new NullRedirector());
    }

    public Ring(String uuid, Executor executor, Redirector redirector) {
        this.uuid = uuid;
        this.executor = executor;
        this.redirector = redirector;
        this.principal = new Principal(uuid, "system", "Internal");
    }

    /**
     * Sets the {@link Registry} for this instance. This is currently done in
     * {@link BlitzConfiguration}
     */
    public void setRegistry(Registry registry) {
        this.registry = registry;
    }

    // Redirector.Context API
    // =========================================================================

    public String uuid() {
        return this.uuid;
    }

    public Principal principal() {
        return this.principal;
    }

    /**
     * Returns the proxy information for the local {@link SessionManager}.
     * 
     * @return
     */
    public String getDirectProxy() {
        return this.directProxy;
    }

    public Ice.Communicator getCommunicator() {
        return this.communicator;
    }

    // Configuration and cluster usage
    // =========================================================================

    /**
     * Typically called from within {@link BlitzConfiguration} after the
     * communicator and adapter have been properly setup.
     */
    public void init(Ice.ObjectAdapter adapter, String directProxy) {
        this.adapter = adapter;
        this.communicator = adapter.getCommunicator();
        this.directProxy = directProxy;

        // Before we add our self we check the validity of the cluster.
        Set<String> nodeUuids = checkCluster();
        if (nodeUuids == null) {
            log.warn("No clusters found. Aborting ring initialization");
            return; // EARLY EXIT!
        }

        try {
            // Now our checking is done, add ourselves.
            Ice.Identity clusterNode = this.communicator.stringToIdentity("ClusterNode/" + uuid);
            this.adapter.add(this, clusterNode); // OK ADAPTER USAGE
            addManager(uuid, directProxy);
            registry.addObject(this.adapter.createDirectProxy(clusterNode));
            nodeUuids.add(uuid);
            redirector.chooseNextRedirect(this, nodeUuids);
        } catch (Exception e) {
            throw new RuntimeException("Cannot register self as node: ", e);
        }
    }

    /**
     * Method called during initialization to get all the active uuids within
     * the cluster, and remove any dead nodes. May return null if lookup fails.
     */
    public Set<String> checkCluster() {

        log.info("Checking cluster");
        ClusterNodePrx[] nodes = registry.lookupClusterNodes();
        if (nodes == null) {
            log.error("Could not lookup nodes. Skipping initialization...");
            return null; // EARLY EXIT
        }

        // Contact each of the cluster. During init this, instance has not been
        // added, so this will not cause a callback. On clusterCheckTrigger,
        // however, it might.
        Set<String> nodeUuids = new HashSet<String>();
        for (int i = 0; i < nodes.length; i++) {
            ClusterNodePrx prx = nodes[i];
            if (prx == null) {
                log.warn("Null proxy found");
                continue;
            } else {
                try {
                    nodeUuids.add(nodes[i].getNodeUuid());
                } catch (Exception e) {
                    log.warn("Error getting uuid from node " + nodes[i] + " -- removing.");
                    registry.removeObjectSafely(prx.ice_getIdentity());
                }
            }
        }
        log.info("Got " + nodeUuids.size() + " cluster uuids : " + nodeUuids);

        // Now any stale nodes (ones not found in the registry) are forcibly
        // removed, since it is assumed they didn't shut down cleanly.
        assertNodes(nodeUuids);

        return nodeUuids;
    }

    public void destroy() {
        try {
            Ice.Identity id = this.communicator.stringToIdentity("ClusterNode/" + uuid);
            registry.removeObjectSafely(id);
            redirector.handleRingShutdown(this, this.uuid);
            int count = closeSessionsForManager(uuid);
            log.info("Removed " + count + " entries for " + uuid);
            log.info("Disconnected from OMERO.cluster");
        } catch (Exception e) {
            log.error("Error stopping ring " + this, e);
        } finally {
            ClusterNodePrx[] nodes = null;
            try {
                // TODO this would be better served with a storm message!
                nodes = registry.lookupClusterNodes();
                if (nodes != null) {
                    for (ClusterNodePrx clusterNodePrx : nodes) {
                        try {
                            clusterNodePrx = ClusterNodePrxHelper.uncheckedCast(clusterNodePrx.ice_oneway());
                            clusterNodePrx.down(this.uuid);
                        } catch (Exception e) {
                            String msg = "Error signaling down to " + clusterNodePrx;
                            log.warn(msg, e);
                        }
                    }
                }
            } catch (Exception e) {
                log.error("Error signaling down to: " + Arrays.deepToString(nodes), e);
            }
        }
    }

    // Cluster Node API
    // =========================================================================

    public String getNodeUuid(Current __current) {
        return this.uuid;
    }

    /**
     * Called when any node goes down. First we try to remove any redirect for
     * that instance. Then we try to install ourselves.
     */
    public void down(String downUuid, Current __current) {
        redirector.handleRingShutdown(this, downUuid);
    }

    // Local usage
    // =========================================================================

    /**
     * Currently only returns false since if the regular password check
     * performed by {@link ome.services.sessions.SessionManager} cannot find the
     * session, then the cluster has no extra information.
     */
    public boolean checkPassword(final String userId) {
        return (Boolean) executor.executeSql(new Executor.SimpleSqlWork(this, "checkPassword") {
            @Transactional(readOnly = true)
            public Object doWork(SqlAction sql) {
                return sql.activeSession(userId);
            }
        });
    }

    /**
     * Delegates to the {@link #redirector} strategy configured for this
     * instance.
     */
    public SessionPrx getProxyOrNull(String userId, Glacier2.SessionControlPrx control, Ice.Current current)
            throws CannotCreateSessionException {
        return redirector.getProxyOrNull(this, userId, control, current);
    }

    public Set<String> knownManagers() {
        return getManagerList(true);
    }

    public void assertNodes(Set<String> nodeUuids) {
        Set<String> managers = knownManagers();

        for (String manager : managers) {
            if (!nodeUuids.contains(manager)) {
                // Also verify this is not ourself, since
                // possibly we haven't finished registration
                // yet
                if (!uuid.equals(manager)) {
                    // And also don't try to purge the original manager.
                    if (!"000000000000000000000000000000000000".equals(manager)) {
                        purgeNode(manager);
                    }
                }
            }
        }
    }

    protected void purgeNode(String manager) {
        log.info("Purging node: " + manager);
        try {
            Ice.Identity id = this.communicator.stringToIdentity("ClusterNode/" + manager);
            registry.removeObjectSafely(id);
            int count = closeSessionsForManager(manager);
            log.info("Removed " + count + " entries with value " + manager);
            setManagerDown(manager);
            log.info("Removed manager: " + manager);
            redirector.handleRingShutdown(this, manager);
            log.info("handleRingShutdown: " + manager);
        } catch (Exception e) {
            log.error("Failed to purge node " + manager, e);
        }
    }

    // Database interactions
    // =========================================================================

    @SuppressWarnings("unchecked")
    public Set<String> getManagerList(final boolean onlyActive) {
        return (Set<String>) executor.execute(principal, new Executor.SimpleWork(this, "getManagerList") {
            @Transactional(readOnly = true)
            public Object doWork(Session session, ServiceFactory sf) {
                List<Node> nodes = sf.getQueryService().findAll(Node.class, null);
                Set<String> nodeIds = new HashSet<String>();
                for (Node node : nodes) {
                    if (onlyActive && node.getDown() != null) {
                        continue; // Remove none active managers
                    }
                    nodeIds.add(node.getUuid());
                }
                return nodeIds;
            }
        });
    }

    /**
     * Assumes that the given manager is no longer available and so will not
     * attempt to call cache.removeSession() since that requires the session to
     * be in memory. Instead directly modifies the database to set the session
     * to closed.
     * 
     * @param managerUuid
     * @return
     */
    @SuppressWarnings("unchecked")
    private int closeSessionsForManager(final String managerUuid) {

        // First look up the sessions in on transaction
        return (Integer) executor.execute(principal,
                new Executor.SimpleWork(this, "executeUpdate - set closed = now()") {
                    @Transactional(readOnly = false)
                    public Object doWork(Session session, ServiceFactory sf) {
                        return getSqlAction().closeNodeSessions(managerUuid);
                    }
                });
    }

    private void setManagerDown(final String managerUuid) {
        executor.execute(principal, new Executor.SimpleWork(this, "setManagerDown") {
            @Transactional(readOnly = false)
            public Object doWork(Session session, ServiceFactory sf) {
                return getSqlAction().closeNode(managerUuid);
            }
        });
    }

    private Node addManager(String managerUuid, String proxyString) {
        final Node node = new Node();
        node.setConn(proxyString);
        node.setUuid(managerUuid);
        node.setUp(new Timestamp(System.currentTimeMillis()));
        return (Node) executor.execute(principal, new Executor.SimpleWork(this, "addManager") {
            @Transactional(readOnly = false)
            public Object doWork(Session session, ServiceFactory sf) {
                return sf.getUpdateService().saveAndReturnObject(node);
            }
        });
    }

}