/*
* RED5 Open Source Flash Server - http://code.google.com/p/red5/
*
* Copyright 2006-2012 by respective authors (see below). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.red5.server.scope;
import java.beans.ConstructorProperties;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Semaphore;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.StandardMBean;
import javax.management.openmbean.CompositeData;
import org.apache.commons.lang3.StringUtils;
import org.red5.server.AttributeStore;
import org.red5.server.Server;
import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.IContext;
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.scope.IBasicScope;
import org.red5.server.api.scope.IBroadcastScope;
import org.red5.server.api.scope.IGlobalScope;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.scope.IScopeAware;
import org.red5.server.api.scope.IScopeHandler;
import org.red5.server.api.scope.ScopeType;
import org.red5.server.api.statistics.IScopeStatistics;
import org.red5.server.api.statistics.support.StatisticsCounter;
import org.red5.server.jmx.mxbeans.ScopeMXBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.style.ToStringCreator;
import org.springframework.jmx.export.annotation.ManagedResource;
/**
* The scope object.
* <p>
* A stateful 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.</p>
* Scope layout:
* <pre>
* /Global scope - Contains application scopes
* /Application scope - Contains room, shared object, and stream scopes
* /Room scope - Contains other room, shared object, and / or stream scopes
* /Shared object scope - Contains shared object
* /Broadcast stream scope - Contains a broadcast stream
* </pre>
*
* @author The Red5 Project (red5@osflash.org)
* @author Paul Gregoire (mondain@gmail.com)
* @author Nathan Smith (nathgs@gmail.com)
*/
@ManagedResource(objectName = "org.red5.server:type=Scope", description = "Scope")
public class Scope extends BasicScope implements IScope, IScopeStatistics, ScopeMXBean {
protected static Logger log = LoggerFactory.getLogger(Scope.class);
/**
* Unset flag constant
*/
private static final int UNSET = -1;
/**
* Auto-start flag
*/
private boolean autoStart = true;
/**
* Child scopes
*/
private final ConcurrentScopeSet<IBasicScope> children = new ConcurrentScopeSet<IBasicScope>();
/**
* Clients and connection map
*/
private final ConcurrentMap<IClient, Set<IConnection>> clients = new ConcurrentHashMap<IClient, Set<IConnection>>();
/**
* Storage for scope attributes
*/
protected final AttributeStore attributes = new AttributeStore();
/**
* Statistics about clients connected to the scope.
*/
protected final StatisticsCounter clientStats = new StatisticsCounter();
/**
* Statistics about connections to the scope.
*/
protected final StatisticsCounter connectionStats = new StatisticsCounter();
/**
* Statistics about sub-scopes.
*/
protected final StatisticsCounter subscopeStats = 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;
/**
* Lock for critical sections, to prevent concurrent modification.
* A "fairness" policy is used wherein the longest waiting thread will be granted access before others.
*/
protected Semaphore lock = new Semaphore(1, true);
/**
* Registered service handlers for this scope. The map is created on-demand
* only if it's accessed for writing.
*/
private volatile ConcurrentMap<String, Object> serviceHandlers;
/**
* Mbean object name.
*/
protected ObjectName oName;
{
creationTime = System.currentTimeMillis();
}
/**
* Creates a scope
*/
@ConstructorProperties(value = { "" })
public Scope() {
super(null, ScopeType.UNDEFINED, null, false);
}
/**
* Creates scope using a Builder
*
* @param builder
*/
@ConstructorProperties({ "builder" })
public Scope(Builder builder) {
super(builder.parent, builder.type, builder.name, builder.persistent);
}
/**
* 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) {
log.debug("Add child: {}", scope);
if (!children.contains(scope)) {
if (scope.isValid()) {
log.debug("Add child scope: {} to {}", scope, this);
// if child scope has no persistence store, use same class as parent
if (scope.getStore() == null) {
try {
if (scope instanceof Scope) {
((Scope) scope).setPersistenceClass(persistenceClass);
}
} catch (Exception error) {
log.error("Could not set persistence class.", error);
}
}
return children.add(scope);
} else {
log.warn("Invalid scope was not added: {}", scope);
}
} else {
log.warn("Child scope already exists");
}
return false;
}
/**
* Connect to scope
*
* @param conn Connection object
* @return <code>true</code> on success, <code>false</code> otherwise
*/
public 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
* parameters. Client associated with connection is added to scope clients set,
* connection is registered as scope event listener.
*
* @param conn Connection object
* @param params Parameters passed with connection
* @return <code>true</code> on success, <code>false</code> otherwise
*/
public boolean connect(IConnection conn, Object[] params) {
log.debug("Connect: {}", conn);
if (hasParent() && !parent.connect(conn, params)) {
return false;
}
if (hasHandler() && !getHandler().connect(conn, this, params)) {
return false;
}
final IClient client = conn.getClient();
if (!conn.isConnected()) {
// timeout while connecting client
return false;
}
// we would not get this far if there is no handler
if (hasHandler() && !getHandler().join(client, this)) {
return false;
}
// checking the connection again? why?
if (!conn.isConnected()) {
// Timeout while connecting client
return false;
}
Set<IConnection> conns = clients.get(client);
if (conns == null) {
conns = new CopyOnWriteArraySet<IConnection>();
clients.put(client, conns);
}
conns.add(conn);
clientStats.increment();
addEventListener(conn);
connectionStats.increment();
IScope connScope = conn.getScope();
log.trace("Connection scope: {}", connScope);
if (this.equals(connScope)) {
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 Builder(this, ScopeType.ROOM, name, false).build();
return addChildScope(scope);
}
/**
* Destroys scope
*/
public void destroy() {
log.debug("Destroy scope");
if (hasParent()) {
parent.removeChildScope(this);
}
if (hasHandler()) {
// Because handler can be null when there is a parent handler
getHandler().stop(this);
}
// kill all child scopes
for (IBasicScope child : children) {
removeChildScope(child);
if (child instanceof Scope) {
((Scope) child).uninit();
}
}
}
/**
* Disconnect connection from scope
*
* @param conn Connection object
*/
public void disconnect(IConnection conn) {
log.debug("Disconnect: {}", 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) {
// Early bail out
removeEventListener(conn);
connectionStats.decrement();
if (hasParent()) {
parent.disconnect(conn);
}
return;
}
// remove it if it exists
final Set<IConnection> conns = clients.remove(client);
if (conns != null) {
// decrement if there was a set of connections
clientStats.decrement();
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 {} on handler {}. {}", new Object[] { conn, handler, e });
}
}
if (conns.isEmpty()) {
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 {} on handler {}. {}", new Object[] { conn, 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) {
Collection<Set<IConnection>> conns = getConnections();
for (Set<IConnection> set : conns) {
for (IConnection conn : set) {
try {
conn.dispatchEvent(event);
} catch (RuntimeException e) {
log.error("Exception during dispatching event: {}", event, e);
}
}
}
}
/** {@inheritDoc} */
public Object getAttribute(String name) {
return attributes.getAttribute(name);
}
/** {@inheritDoc} */
public boolean setAttribute(String name, Object value) {
return attributes.setAttribute(name, value);
}
/** {@inheritDoc} */
public boolean hasAttribute(String name) {
return attributes.hasAttribute(name);
}
/** {@inheritDoc} */
public boolean removeAttribute(String name) {
return attributes.removeAttribute(name);
}
/** {@inheritDoc} */
public Set<String> getAttributeNames() {
return attributes.getAttributeNames();
}
/** {@inheritDoc} */
public Map<String, Object> getAttributes() {
return attributes.getAttributes();
}
/** {@inheritDoc} */
public int getActiveClients() {
return clients.size();
}
/** {@inheritDoc} */
public int getActiveConnections() {
return connectionStats.getCurrent();
}
/** {@inheritDoc} */
public int getActiveSubscopes() {
return subscopeStats.getCurrent();
}
/**
* Return the broadcast scope for a given name
*
* @param name
* @return broadcast scope or null if not found
*/
public IBroadcastScope getBroadcastScope(String name) {
for (IBasicScope child : children) {
if (child.getType().equals(ScopeType.BROADCAST) && child.getName().equals(name)) {
log.debug("Returning broadcast scope");
return (IBroadcastScope) child;
}
}
return null;
}
/**
* Return base scope of given type with given name
*
* @param type Scope type
* @param name Scope name
* @return Basic scope object
*/
public IBasicScope getBasicScope(ScopeType type, String name) {
for (IBasicScope child : children) {
if (child.getType().equals(type) && child.getName().equals(name)) {
log.debug("Returning basic scope");
return child;
}
}
return null;
}
/**
* Return basic scope names matching given type
*
* @param type Scope type
* @return set of scope names
*/
public Set<String> getBasicScopeNames(ScopeType type) {
if (type != null) {
Set<String> names = new HashSet<String>();
for (IBasicScope child : children) {
if (child.getType().equals(type)) {
names.add(child.getName());
}
}
return names;
} else {
return getScopeNames();
}
}
/**
* Return current thread context classloader
*
* @return Current thread context classloader
*/
public ClassLoader getClassLoader() {
return getContext().getClassLoader();
}
/**
* 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 Collection<Set<IConnection>> getConnections() {
return clients.values();
}
/**
* 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) {
for (IBasicScope child : children) {
if (child.getName().equals(name)) {
log.debug("Returning child scope");
return (IScope) child;
}
}
return null;
}
/**
* Return child scope names iterator
*
* @return Child scope names iterator
*/
public Set<String> getScopeNames() {
log.debug("Children: {}", children);
Set<String> names = new HashSet<String>();
for (IBasicScope child : children) {
names.add(child.getName());
}
return names;
}
/**
* 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
*/
@SuppressWarnings("unchecked")
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) {
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) {
log.debug("Has child scope? {} in {}", name, this);
for (IBasicScope child : children) {
if (child.getName().equals(name)) {
log.debug("Child scope exists");
return true;
}
}
log.debug("Child scope does not exist");
return false;
}
/**
* 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(ScopeType type, String name) {
log.debug("Has child scope? {} in {}", name, this);
for (IBasicScope child : children) {
if (child.getName().equals(name) && child.getType().equals(type)) {
log.debug("Child scope exists");
return true;
}
}
return false;
}
/**
* 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() {
log.debug("Init scope: {} parent: {}", name, parent);
if (hasParent()) {
if (parent.addChildScope(this)) {
log.debug("Scope added to parent");
} else {
return;
}
} else {
log.debug("Scope has no parent");
}
if (autoStart) {
start();
}
}
/**
* Uninitialize scope and unregister from parent.
*/
public void uninit() {
log.debug("Un-init scope");
for (IBasicScope child : children) {
if (child instanceof Scope) {
((Scope) child).uninit();
}
}
stop();
if (hasParent()) {
if (parent.hasChildScope(name)) {
parent.removeChildScope(this);
}
}
}
/**
* Check if scope is enabled
*
* @return <code>true</code> if scope is enabled, <code>false</code>
* otherwise
*/
public boolean isEnabled() {
return enabled;
}
/**
* Here for JMX only, uses isEnabled()
*/
public boolean getEnabled() {
return isEnabled();
}
/**
* 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;
}
/**
* Here for JMX only, uses isEnabled()
*/
public boolean getRunning() {
return isRunning();
}
/**
* 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) {
log.debug("removeChildScope: {}", scope);
if (hasChildScope(scope.getName())) {
// remove from parent
children.remove(scope);
if (scope instanceof Scope) {
unregisterJMX();
}
}
}
/**
* Removes all the child scopes
*/
public void removeChildren() {
for (IBasicScope child : children) {
removeChildScope(child);
}
}
/**
* 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) {
log.debug("Set context: {}", 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) {
log.debug("Set handler: {}", 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) {
log.debug("Set name: {}", name);
if (oName != null) {
unregisterJMX();
}
this.name = name;
if (StringUtils.isNotBlank(name)) {
registerJMX();
}
}
/**
* Setter for parent scope
*
* @param parent Parent scope
*/
public void setParent(IScope parent) {
log.debug("Set parent scope: {}", 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) {
store = PersistenceUtils.getPersistenceStore(this, persistenceClass);
}
}
/**
* Starts scope
*
* @return <code>true</code> if scope has handler and it's start method
* returned true, <code>false</code> otherwise
*/
public boolean start() {
log.debug("Start scope");
boolean result = false;
if (enabled && !running) {
// check for any handlers
if (hasHandler()) {
try {
lock.acquire();
// if we dont have a handler of our own dont try to start it
if (handler != null) {
result = handler.start(this);
}
} catch (Throwable e) {
log.error("Could not start scope {} {}", this, e);
} finally {
lock.release();
}
} else {
// Always start scopes without handlers
log.debug("Scope {} has no handler, allowing start", this);
result = true;
}
running = result;
}
return result;
}
/**
* Stops scope
*/
public void stop() {
log.debug("Stop scope");
if (enabled && running && handler != null) {
try {
lock.acquire();
// if we dont have a handler of our own dont try to stop it
handler.stop(this);
} catch (Throwable e) {
log.error("Could not stop scope {}", this, e);
} finally {
lock.release();
}
}
running = false;
}
/**
* {@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;
}
}
//for debugging
public void dump() {
if (log.isDebugEnabled()) {
log.debug("Scope: {} {}", this.getClass().getName(), this);
log.debug("Running: {}", running);
if (hasParent()) {
log.debug("Parent: {}", parent);
Set<String> names = parent.getBasicScopeNames(null);
log.debug("Sibling count: {}", names.size());
for (String sib : names) {
log.debug("Siblings - {}", sib);
}
names = null;
}
log.debug("Handler: {}", handler);
log.debug("Child count: {}", children.size());
for (IBasicScope entry : children) {
log.debug("Child: {}", entry);
}
}
}
protected void registerJMX() {
// register with jmx
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
try {
String cName = this.getClass().getName();
if (cName.indexOf('.') != -1) {
cName = cName.substring(cName.lastIndexOf('.')).replaceFirst("[\\.]", "");
}
oName = new ObjectName(String.format("org.red5.server:type=%s,name=%s", cName, name));
mbs.registerMBean(new StandardMBean(this, ScopeMXBean.class, true), oName);
} catch (Exception e) {
log.warn("Error on jmx registration", e);
}
}
protected void unregisterJMX() {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
if (oName != null && mbs.isRegistered(oName)) {
try {
mbs.unregisterMBean(oName);
} catch (Exception e) {
log.warn("Exception unregistering: {}", oName, e);
}
oName = null;
}
}
/**
* Allows for reconstruction via CompositeData.
*
* @param cd composite data
* @return Scope class instance
*/
public static Scope from(CompositeData cd) {
IScope parent = null;
ScopeType type = ScopeType.UNDEFINED;
String name = null;
boolean persistent = false;
if (cd.containsKey("parent")) {
parent = (IScope) cd.get("parent");
}
if (cd.containsKey("type")) {
type = (ScopeType) cd.get("type");
}
if (cd.containsKey("name")) {
name = (String) cd.get("name");
}
if (cd.containsKey("persistent")) {
persistent = (Boolean) cd.get("persistent");
}
return new Scope(new Builder(parent, type, name, persistent));
}
@SuppressWarnings({ "hiding", "serial" })
private final class ConcurrentScopeSet<IBasicScope> extends CopyOnWriteArraySet<IBasicScope> {
@Override
public boolean add(IBasicScope scope) {
if (scope instanceof IScope) {
return add((IScope) scope);
} else {
log.debug("Adding basic scope to scope set");
if (hasHandler() && !getHandler().addChildScope((org.red5.server.api.scope.IBasicScope) scope)) {
log.debug("Failed to add child scope: {} to {}", scope, this);
return false;
}
boolean added = super.add(scope);
if (added) {
subscopeStats.increment();
}
return added;
}
}
public boolean add(IScope scope) {
log.debug("Adding scope to scope set");
if (hasHandler()) {
// add the scope to the handler
if (!getHandler().addChildScope(scope)) {
log.debug("Failed to add child scope: {} to {}", scope, this);
return false;
}
// start the scope
if (!getHandler().start((IScope) scope)) {
log.debug("Failed to start child scope: {} in {}", scope, this);
return false;
}
}
// add the entry
@SuppressWarnings("unchecked")
boolean added = super.add((IBasicScope) scope);
if (added) {
subscopeStats.increment();
// post notification
IServer server = getServer();
((Server) server).notifyScopeCreated((IScope) scope);
}
return added;
}
@Override
public boolean remove(Object scope) {
log.debug("Remove child scope: {}", scope);
if (hasHandler()) {
log.debug("Remove child scope");
getHandler().removeChildScope((org.red5.server.api.scope.IBasicScope) scope);
}
if (scope instanceof IScope) {
if (hasHandler()) {
getHandler().stop((IScope) scope);
}
// remove all children
((IScope) scope).removeChildren();
// post notification
final IServer server = getServer();
if (server instanceof Server) {
((Server) server).notifyScopeRemoved((IScope) scope);
}
}
// remove the entry
boolean removed = super.remove(scope);
if (removed) {
subscopeStats.decrement();
}
return removed;
}
}
/**
* Builder pattern
*/
public final static class Builder {
private IScope parent;
private ScopeType type;
private String name;
private boolean persistent;
public Builder(IScope parent, ScopeType type, String name, boolean persistent) {
this.parent = parent;
this.type = type;
this.name = name;
this.persistent = persistent;
}
public Scope build() {
return new Scope(this);
}
}
}