org.red5.server.Scope.java Source code

Java tutorial

Introduction

Here is the source code for org.red5.server.Scope.java

Source

package org.red5.server;

/*
 * RED5 Open Source Flash Server - http://www.osflash.org/red5
 *
 * Copyright (c) 2006-2007 by respective authors (see below). All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any later
 * version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this library; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.red5.server.api.IBasicScope;
import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.IContext;
import org.red5.server.api.IGlobalScope;
import org.red5.server.api.IScope;
import org.red5.server.api.IScopeAware;
import org.red5.server.api.IScopeHandler;
import org.red5.server.api.IServer;
import org.red5.server.api.event.IEvent;
import org.red5.server.api.persistence.PersistenceUtils;
import org.red5.server.api.statistics.IScopeStatistics;
import org.red5.server.api.statistics.support.StatisticsCounter;
import org.red5.server.jmx.JMXAgent;
import org.springframework.core.io.Resource;
import org.springframework.core.style.ToStringCreator;

/**
 * The scope object.
 *
 * A statefull object shared between a group of clients connected to the same
 * context path. Scopes are arranged in a hierarchical way, so its possible for
 * a scope to have a parent. If a client is connect to a scope then they are
 * also connected to its parent scope. The scope object is used to access
 * resources, shared object, streams, etc.
 *
 * The following are all names for scopes: application, room, place, lobby.
 *
 * @author The Red5 Project (red5@osflash.org)
 */
public class Scope extends BasicScope implements IScope, IScopeStatistics, ScopeMBean {
    /**
     * Iterates through connections
     */
    class ConnectionIterator implements Iterator<IConnection> {
        /**
         * Connections iterator
         */
        private Iterator<IConnection> connIterator;

        /**
         * Current connection
         */
        private IConnection current;

        /**
         * Set iterator
         */
        private Iterator<Set<IConnection>> setIterator;

        /**
         * Creates connection iterator
         */
        public ConnectionIterator() {
            setIterator = clients.values().iterator();
        }

        /**
         * {@inheritDoc}
         */
        public boolean hasNext() {
            if (connIterator != null && connIterator.hasNext()) {
                // More connections for this client
                return true;
            }

            if (!setIterator.hasNext()) {
                // No more clients
                return false;
            }

            connIterator = setIterator.next().iterator();
            while (connIterator != null) {
                if (connIterator.hasNext()) {
                    // Found client with connections
                    return true;
                }

                if (!setIterator.hasNext()) {
                    // No more clients
                    return false;
                }

                // Advance to next client
                connIterator = setIterator.next().iterator();
            }
            return false;
        }

        /**
         * {@inheritDoc}
         */
        public IConnection next() {
            if (connIterator == null || !connIterator.hasNext()) {
                if (!setIterator.hasNext()) {
                    // No more clients
                    throw new NoSuchElementException();
                }

                connIterator = setIterator.next().iterator();
                while (!connIterator.hasNext()) {
                    // Client has no connections, search next one
                    if (!setIterator.hasNext()) {
                        // No more clients
                        throw new NoSuchElementException();
                    }

                    connIterator = setIterator.next().iterator();
                }
            }
            // Always of type IConnection, no need to cast
            current = connIterator.next();
            return current;
        }

        /**
         * {@inheritDoc}
         */
        public void remove() {
            if (current != null) {
                disconnect(current);
            }
        }

    }

    /**
     * Iterator that filters strings by given prefix
     */
    class PrefixFilteringStringIterator implements Iterator<String> {
        /**
         * Iterator
         */
        private Iterator<String> iterator;

        /**
         * Next object
         */
        private String next;

        /**
         * Prefix
         */
        private String prefix;

        /**
         * Creates prefix filtering string iterator from iterator and prefix
         * @param iterator             Iterator
         * @param prefix               Prefix
         */
        public PrefixFilteringStringIterator(Iterator<String> iterator, String prefix) {
            this.iterator = iterator;
            this.prefix = prefix;
        }

        /** {@inheritDoc} */
        public boolean hasNext() {
            if (next != null) {
                return true;
            }
            do {
                next = (iterator.hasNext()) ? iterator.next() : null;
            } while (next != null && !next.startsWith(prefix));
            return next != null;
        }

        /** {@inheritDoc} */
        public String next() {
            if (next != null) {
                final String result = next;
                next = null;
                return result.substring(prefix.length());
            }
            if (hasNext()) {
                return next();
            } else {
                return null;
            }
        }

        /** {@inheritDoc} */
        public void remove() {
            throw new UnsupportedOperationException();
        }

    }

    /**
     * Logger
     */
    protected static Log log = LogFactory.getLog(Scope.class.getName());

    /**
     * Scope type constant
     */
    private static final String TYPE = "scope";

    /**
     * Unset flag constant
     */
    private static final int UNSET = -1;

    /**
     * Autostart flag
     */
    private boolean autoStart = true;

    /**
     * Child scopes map (child scopes are named)
     */
    private volatile Map<String, IBasicScope> children = new ConcurrentHashMap<String, IBasicScope>();

    /**
     * Clients and connection map
     */
    private volatile Map<IClient, Set<IConnection>> clients = new ConcurrentHashMap<IClient, Set<IConnection>>();

    /**
     * Statistics about clients connected to the scope.
     */
    private StatisticsCounter clientStats = new StatisticsCounter();

    /**
     * Statistics about connections to the scope.
     */
    private StatisticsCounter connectionStats = new StatisticsCounter();

    /**
     * Scope context
     */
    private IContext context;

    /**
     * Timestamp the scope was created.
     */
    private long creationTime;

    /**
     * Scope nesting depth, unset by default
     */
    private int depth = UNSET;

    /**
     * Whether scope is enabled
     */
    private boolean enabled = true;

    /**
     * Scope handler
     */
    private IScopeHandler handler;

    /**
     * Whether scope is running
     */
    private boolean running;

    /**
     * Registered service handlers for this scope. The map is created on-demand only
     * if it's accessed for writing.
     */
    private volatile Map<String, Object> serviceHandlers;

    /**
     * Statistics about subscopes.
     */
    private StatisticsCounter subscopeStats = new StatisticsCounter();

    /**
     * Creates unnamed scope
     */
    public Scope() {
        this(null);
        creationTime = System.currentTimeMillis();
    }

    /**
     * Creates scope with given name
     * @param name                      Scope name
     */
    public Scope(String name) {
        super(null, TYPE, name, false);
        creationTime = System.currentTimeMillis();
    }

    /**
     * Add child scope to this scope
     * @param scope        Child scope
     * @return             <code>true</code> on success (if scope has handler and it accepts child scope addition), <code>false</code> otherwise
     */
    public boolean addChildScope(IBasicScope scope) {
        if (scope.getStore() == null) {
            // Child scope has no persistence store, use same class as parent.
            try {
                if (scope instanceof Scope) {
                    ((Scope) scope).setPersistenceClass(this.persistenceClass);
                }
            } catch (Exception error) {
                log.error("Could not set persistence class.", error);
            }
        }
        if (hasHandler() && !getHandler().addChildScope(scope)) {
            if (log.isDebugEnabled()) {
                log.debug("Failed to add child scope: " + scope + " to " + this);
            }
            return false;
        }
        if (scope instanceof IScope) {
            // start the scope
            if (hasHandler() && !getHandler().start((IScope) scope)) {
                if (log.isDebugEnabled()) {
                    log.debug("Failed to start child scope: " + scope + " in " + this);
                }
                return false;
            }
        }
        if (scope instanceof IScope) {
            final IServer server = getServer();
            if (server instanceof Server) {
                ((Server) server).notifyScopeCreated((IScope) scope);
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("Add child scope: " + scope + " to " + this);
        }
        children.put(scope.getType() + SEPARATOR + scope.getName(), scope);
        return true;
    }

    /**
     * Connect to scope
     * @param conn                  Connection object
     * @return                      <code>true</code> on success, <code>false</code> otherwise
     */
    public synchronized boolean connect(IConnection conn) {
        return connect(conn, null);
    }

    /**
     * Connect to scope with parameters. To successfully connect to scope it must have handler that will accept
     * this connection with given set of params. Client associated with connection is added to scope clients set,
     * connection is registred as scope event listener.
     *
     * @param conn                  Connection object
     * @param params                Params passed with connection
     * @return                      <code>true</code> on success, <code>false</code> otherwise
     */
    public synchronized boolean connect(IConnection conn, Object[] params) {
        //log.debug("Connect: "+conn+" to "+this);
        //log.debug("has handler? "+hasHandler());
        if (hasParent() && !parent.connect(conn, params)) {
            return false;
        }
        if (hasHandler() && !getHandler().connect(conn, this, params)) {
            return false;
        }
        final IClient client = conn.getClient();
        //log.debug("connected to: "+this);
        if (!clients.containsKey(client)) {
            //log.debug("Joining: "+this);
            if (hasHandler() && !getHandler().join(client, this)) {
                return false;
            }
            final Set<IConnection> conns = new HashSet<IConnection>();
            conns.add(conn);
            clients.put(conn.getClient(), conns);
            log.debug("adding client");
            clientStats.increment();
        } else {
            final Set<IConnection> conns = clients.get(client);
            conns.add(conn);
        }
        addEventListener(conn);
        connectionStats.increment();

        if (this.equals(conn.getScope())) {
            final IServer server = getServer();
            if (server instanceof Server) {
                ((Server) server).notifyConnected(conn);
            }
        }
        return true;
    }

    /**
     * Create child scope with given name
     * @param name           Child scope name
     * @return               <code>true</code> on success, <code>false</code> otherwise
     */
    public boolean createChildScope(String name) {
        final Scope scope = new Scope(name);
        scope.setParent(this);
        subscopeStats.increment();
        return addChildScope(scope);
    }

    /**
     * Destroys scope
     */
    public void destory() {
        if (hasParent()) {
            parent.removeChildScope(this);
        }
        if (hasHandler()) {
            handler.stop(this);
            // TODO:  kill all child scopes
        }
    }

    /**
     * Disconnect connection from scope
     * @param conn            Connection object
     */
    public synchronized void disconnect(IConnection conn) {
        // We call the disconnect handlers in reverse order they were called
        // during connection, i.e. roomDisconnect is called before
        // appDisconnect.
        final IClient client = conn.getClient();
        if (client != null && clients.containsKey(client)) {
            final Set<IConnection> conns = clients.get(client);
            conns.remove(conn);
            IScopeHandler handler = null;
            if (hasHandler()) {
                handler = getHandler();
                try {
                    handler.disconnect(conn, this);
                } catch (Exception e) {
                    log.error("Error while executing \"disconnect\" for connection " + conn + " on handler "
                            + handler, e);
                }
            }

            if (conns.isEmpty()) {
                clients.remove(client);
                clientStats.decrement();
                if (handler != null) {
                    try {
                        // there may be a timeout here ?
                        handler.leave(client, this);
                    } catch (Exception e) {
                        log.error("Error while executing \"leave\" for client " + client + " on handler " + handler,
                                e);
                    }
                }
            }
            removeEventListener(conn);
            connectionStats.decrement();

            if (this.equals(conn.getScope())) {
                final IServer server = getServer();
                if (server instanceof Server) {
                    ((Server) server).notifyDisconnected(conn);
                }
            }
        }
        if (hasParent()) {
            parent.disconnect(conn);
        }
    }

    /** {@inheritDoc} */
    @Override
    public void dispatchEvent(IEvent event) {
        Iterator<IConnection> conns = getConnections();
        while (conns.hasNext()) {
            try {
                conns.next().dispatchEvent(event);
            } catch (RuntimeException e) {
                log.error(e);
            }
        }
    }

    /** {@inheritDoc} */
    public int getActiveClients() {
        return clients.size();
    }

    /** {@inheritDoc} */
    public int getActiveConnections() {
        return connectionStats.getCurrent();
    }

    /** {@inheritDoc} */
    public int getActiveSubscopes() {
        return subscopeStats.getCurrent();
    }

    /**
     * Return base scope of given type with given name
     * @param type           Scope type
     * @param name           Scope name
     * @return               Basic scope object
     */
    public IBasicScope getBasicScope(String type, String name) {
        return children.get(type + SEPARATOR + name);
    }

    /**
     * Return basic scope names iterator
     * @param type           Scope type
     * @return               Iterator
     */
    public Iterator<String> getBasicScopeNames(String type) {
        if (type == null) {
            return children.keySet().iterator();
        } else {
            return new PrefixFilteringStringIterator(children.keySet().iterator(), type + SEPARATOR);
        }
    }

    /**
     * Return current thread context classloader
     * @return         Current thread context classloader
     */
    public ClassLoader getClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * Return set of clients
     * @return                   Set of clients bound to scope
     */
    public Set<IClient> getClients() {
        return clients.keySet();
    }

    /**
     * Return connection iterator
     * @return                Connections iterator
     */
    public Iterator<IConnection> getConnections() {
        return new ConnectionIterator();
    }

    /**
     * Return scope context. If scope doesn't have context, parent's context is returns, and so forth.
     * @return                   Scope context or parent context
     */
    public IContext getContext() {
        if (!hasContext() && hasParent()) {
            log.debug("returning parent context");
            return parent.getContext();
        } else {
            log.debug("returning context");
            return context;
        }
    }

    /**
     * Return scope context path
     * @return                   Scope context path
     */
    public String getContextPath() {
        if (hasContext()) {
            return "";
        } else if (hasParent()) {
            return parent.getContextPath() + '/' + name;
        } else {
            return null;
        }
    }

    /** {@inheritDoc} */
    public long getCreationTime() {
        return creationTime;
    }

    /**
     * return scope depth
     * @return              Scope depth
     */
    @Override
    public int getDepth() {
        if (depth == UNSET) {
            if (hasParent()) {
                depth = parent.getDepth() + 1;
            } else {
                depth = 0;
            }
        }
        return depth;
    }

    /**
     * Return scope handler or parent's scope handler if this scope doesn't have one
     * @return                     Scope handler (or parent's one)
     */
    public IScopeHandler getHandler() {
        if (handler != null) {
            return handler;
        } else if (hasParent()) {
            return getParent().getHandler();
        } else {
            return null;
        }
    }

    /** {@inheritDoc} */
    public int getMaxClients() {
        return clientStats.getMax();
    }

    /** {@inheritDoc} */
    public int getMaxConnections() {
        return connectionStats.getMax();
    }

    /** {@inheritDoc} */
    public int getMaxSubscopes() {
        return subscopeStats.getMax();
    }

    /**
     * Return parent scope
     * @return                      Parent scope
     */
    @Override
    public IScope getParent() {
        return parent;
    }

    /**
     * Return scope path calculated from parent path and parent scope name
     * @return                   Scope path
     */
    @Override
    public String getPath() {
        if (hasParent()) {
            return parent.getPath() + '/' + parent.getName();
        } else {
            return "";
        }
    }

    /**
     * Return resource located at given path
     * @param path           Resource path
     * @return               Resource
     */
    public Resource getResource(String path) {
        if (hasContext()) {
            return context.getResource(path);
        }
        return getContext().getResource(getContextPath() + '/' + path);
    }

    /**
     * Return array of resources from path string, usually used with pattern path
     * @param path           Resources path
     * @return               Resources
     * @throws IOException   I/O exception
     */
    public Resource[] getResources(String path) throws IOException {
        if (hasContext()) {
            return context.getResources(path);
        }
        return getContext().getResources(getContextPath() + '/' + path);
    }

    /**
     * Return child scope by name
     * @param name           Scope name
     * @return               Child scope with given name
     */
    public IScope getScope(String name) {
        return (IScope) children.get(TYPE + SEPARATOR + name);
    }

    /**
     * Return child scope names iterator
     * @return                   Child scope names iterator
     */
    public Iterator<String> getScopeNames() {
        return new PrefixFilteringStringIterator(children.keySet().iterator(), "scope");
    }

    /**
     * Return service handler by name
     * @param name        Handler name
     * @return            Service handler with given name
     */
    public Object getServiceHandler(String name) {
        Map<String, Object> serviceHandlers = getServiceHandlers(false);
        if (serviceHandlers == null) {
            return null;
        }
        return serviceHandlers.get(name);
    }

    /**
     * Return set of service handler names. Removing entries from the
     * set unregisters the corresponding service handler.
     * @return            Set of service handler names
     */
    public Set<String> getServiceHandlerNames() {
        Map<String, Object> serviceHandlers = getServiceHandlers(false);
        if (serviceHandlers == null) {
            return Collections.EMPTY_SET;
        }
        return serviceHandlers.keySet();
    }

    /**
     * Return map of service handlers. The map is created if it doesn't exist yet.
     * @return                Map of service handlers
     */
    protected Map<String, Object> getServiceHandlers() {
        return getServiceHandlers(true);
    }

    /**
     * Return map of service handlers and optionally created it if it doesn't exist.
     * @param allowCreate     Should the map be created if it doesn't exist?
     * @return                Map of service handlers
     */
    protected Map<String, Object> getServiceHandlers(boolean allowCreate) {
        if (serviceHandlers == null) {
            if (!allowCreate)
                return null;

            // Only synchronize if potentially needs to be created
            synchronized (this) {
                if (serviceHandlers == null) {
                    serviceHandlers = new ConcurrentHashMap<String, Object>();
                }
            }
        }
        return serviceHandlers;
    }

    /** {@inheritDoc} */
    public IScopeStatistics getStatistics() {
        return this;
    }

    /** {@inheritDoc} */
    public int getTotalClients() {
        return clientStats.getTotal();
    }

    /** {@inheritDoc} */
    public int getTotalConnections() {
        return connectionStats.getTotal();
    }

    /** {@inheritDoc} */
    public int getTotalSubscopes() {
        return subscopeStats.getTotal();
    }

    /**
     * Handles event. To be implemented in subclasses.
     * @param event          Event to handle
     * @return               <code>true</code> on success, <code>false</code> otherwise
     */
    @Override
    public boolean handleEvent(IEvent event) {
        return false;
    }

    /**
     * Check whether scope has child scope with given name
     * @param name               Child scope name
     * @return                   <code>true</code> if scope has child node with given name, <code>false</code> otherwise
     */
    public boolean hasChildScope(String name) {
        if (log.isDebugEnabled()) {
            log.debug("Has child scope? " + name + " in " + this);
        }
        return children.containsKey(TYPE + SEPARATOR + name);
    }

    /**
     * Check whether scope has child scope with given name and type
     * @param type               Child scope type
     * @param name               Child scope name
     * @return                   <code>true</code> if scope has child node with given name and type, <code>false</code> otherwise
     */
    public boolean hasChildScope(String type, String name) {
        return children.containsKey(type + SEPARATOR + name);
    }

    /**
     * Check if scope has a context
     * @return                   <code>true</code> if scope has context, <code>false</code> otherwise
     */
    public boolean hasContext() {
        return context != null;
    }

    /**
     * Check if scope or it's parent has handler
     * @return                     <code>true</code> if scope or it's parent scope has a handler, <code>false</code> otherwise
     */
    public boolean hasHandler() {
        return (handler != null || (hasParent() && getParent().hasHandler()));
    }

    /**
     * Check if scope has parent scope
     * @return                      <code>true</code> if scope has parent scope, <code>false</code> otherwise`
     */
    @Override
    public boolean hasParent() {
        return (parent != null);
    }

    /**
     * Initialization actions, start if autostart is set to <code>true</code>
     */
    public void init() {
        if (hasParent()) {
            if (!parent.hasChildScope(name)) {
                if (!parent.addChildScope(this)) {
                    return;
                }
            }
        }
        if (autoStart) {
            start();
        }
    }

    /**
     * Check if scope is enabled
     * @return                  <code>true</code> if scope is enabled, <code>false</code> otherwise
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     * Check if scope is in running state
     * @return                   <code>true</code> if scope is in running state, <code>false</code> otherwise
     */
    public boolean isRunning() {
        return running;
    }

    /**
     * Child scopes iterator
     * @return               Child scopes iterator
     */
    @Override
    public Iterator<IBasicScope> iterator() {
        return children.values().iterator();
    }

    /**
     * Looks up connections for client
     * @param client          Client
     * @return                Connection
     */
    public Set<IConnection> lookupConnections(IClient client) {
        return clients.get(client);
    }

    /**
     * Register service handler by name
     * @param name       Service handler name
     * @param handler    Service handler
     */
    public void registerServiceHandler(String name, Object handler) {
        Map<String, Object> serviceHandlers = getServiceHandlers();
        serviceHandlers.put(name, handler);
    }

    /**
     * Removes child scope
     * @param scope       Child scope to remove
     */
    public void removeChildScope(IBasicScope scope) {
        if (scope instanceof IScope) {
            if (hasHandler()) {
                getHandler().stop((IScope) scope);
            }
            subscopeStats.decrement();
        }
        children.remove(scope.getType() + SEPARATOR + scope.getName());
        if (hasHandler()) {
            log.debug("Remove child scope");
            getHandler().removeChildScope(scope);
        }

        if (scope instanceof IScope) {
            final IServer server = getServer();
            if (server instanceof Server) {
                ((Server) server).notifyScopeRemoved((IScope) scope);
            }
        }
    }

    /**
     * Setter for autostart flag
     * @param autoStart         Autostart flag value
     */
    public void setAutoStart(boolean autoStart) {
        this.autoStart = autoStart;
    }

    /**
     * Setter for child load path. Should be implemented in subclasses?
     * @param pattern            Load path pattern
     */
    public void setChildLoadPath(String pattern) {

    }

    /**
     * Setter for context
     * @param context           Context object
     */
    public void setContext(IContext context) {
        this.context = context;
    }

    /**
     * Set scope depth
     * @param depth         Scope depth
     */
    public void setDepth(int depth) {
        this.depth = depth;
    }

    /**
     * Enable or disable scope by setting enable flag
     * @param enabled            Enable flag value
     */
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    /**
     * Setter for scope event handler
     * @param handler           Event handler
     */
    public void setHandler(IScopeHandler handler) {
        this.handler = handler;
        if (handler instanceof IScopeAware) {
            ((IScopeAware) handler).setScope(this);
        }
    }

    /**
     * Setter for scope name
     * @param name               Scope name
     */
    @Override
    public void setName(String name) {
        this.name = name;

        JMXAgent.registerMBean(this, this.getClass().getName(), ScopeMBean.class, name);
    }

    /**
     * Setter for parent scope
     * @param parent              Parent scope
     */
    public void setParent(IScope parent) {
        this.parent = parent;
    }

    /**
     * Set scope persistence class
     *
     * @param persistenceClass       Scope's persistence class
     * @throws Exception             Exception
     */
    public void setPersistenceClass(String persistenceClass) throws Exception {
        this.persistenceClass = persistenceClass;
        if (persistenceClass != null) {
            setStore(PersistenceUtils.getPersistenceStore(this, persistenceClass));
        } else {
            setStore(null);
        }
    }

    /**
     * Starts scope
     * @return     <code>true</code> if scope has handler and it's start method returned true, <code>false</code> otherwise
     */
    public boolean start() {
        return enabled && !running && !(hasHandler() && !handler.start(this));
    }

    /**
     * Stops scope
     */
    public void stop() {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        final ToStringCreator tsc = new ToStringCreator(this);
        return tsc.append("Depth", getDepth()).append("Path", getPath()).append("Name", getName()).toString();
    }

    /**
     * Unregisters service handler by name
     * @param name        Service handler name
     */
    public void unregisterServiceHandler(String name) {
        Map<String, Object> serviceHandlers = getServiceHandlers(false);
        if (serviceHandlers == null) {
            return;
        }
        serviceHandlers.remove(name);
    }

    /**
     * Return the server instance connected to this scope.
     * 
     * @return the server instance
     */
    public IServer getServer() {
        if (!hasParent())
            return null;

        final IScope parent = getParent();
        if (parent instanceof Scope) {
            return ((Scope) parent).getServer();
        } else if (parent instanceof IGlobalScope) {
            return ((IGlobalScope) parent).getServer();
        } else {
            return null;
        }
    }

}