/* * 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; import java.util.Iterator; 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.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.red5.server.api.IConnection; import org.red5.server.api.IServer; import org.red5.server.api.listeners.IConnectionListener; import org.red5.server.api.listeners.IScopeListener; import org.red5.server.api.scope.IGlobalScope; import org.red5.server.api.scope.IScope; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.style.ToStringCreator; /** * Red5 server core class implementation. */ public class Server implements IServer, ApplicationContextAware, InitializingBean, DisposableBean { // Initialize Logging protected static Logger log = LoggerFactory.getLogger(Server.class); /** * Executor service used to provide asynchronous notifications. */ private static ExecutorService notifier; /** * List of global scopes */ protected ConcurrentMap<String, IGlobalScope> globals = new ConcurrentHashMap<String, IGlobalScope>(); /** * Mappings */ protected ConcurrentMap<String, String> mapping = new ConcurrentHashMap<String, String>(); /** * Spring application context */ protected ApplicationContext applicationContext; /** * Constant for slash */ protected static final String SLASH = "/"; /** * Constant for empty string */ protected static final String EMPTY = ""; public Set<IScopeListener> scopeListeners = new CopyOnWriteArraySet<IScopeListener>(); public Set<IConnectionListener> connectionListeners = new CopyOnWriteArraySet<IConnectionListener>(); //number of threads in the notifier pool private int notifierThreadPoolSize = 4; /** * Setter for Spring application context * * @param applicationContext Application context */ public void setApplicationContext(ApplicationContext applicationContext) { log.debug("Setting application context"); this.applicationContext = applicationContext; } /** * Initialization section. */ public void afterPropertiesSet() throws Exception { Server.notifier = Executors.newFixedThreadPool(notifierThreadPoolSize); } /** * Destruction section. */ public void destroy() throws Exception { //disable new tasks from being submitted notifier.shutdown(); try { //wait a while for existing tasks to terminate if (!notifier.awaitTermination(3, TimeUnit.SECONDS)) { notifier.shutdownNow(); // cancel currently executing tasks //wait a while for tasks to respond to being canceled if (!notifier.awaitTermination(3, TimeUnit.SECONDS)) { System.err.println("Notifier pool did not terminate"); } } } catch (InterruptedException ie) { // re-cancel if current thread also interrupted notifier.shutdownNow(); // preserve interrupt status Thread.currentThread().interrupt(); } } /** * Return scope key. Scope key consists of host name concatenated with * context path by slash symbol * * @param hostName Host name * @param contextPath Context path * @return Scope key as string */ protected String getKey(String hostName, String contextPath) { if (hostName == null) { hostName = EMPTY; } if (contextPath == null) { contextPath = EMPTY; } return hostName + SLASH + contextPath; } /** * Does global scope lookup for host name and context path * * @param hostName Host name * @param contextPath Context path * @return Global scope */ public IGlobalScope lookupGlobal(String hostName, String contextPath) { log.trace("{}", this); log.debug("Lookup global scope - host name: {} context path: {}", hostName, contextPath); // Init mappings key String key = getKey(hostName, contextPath); // If context path contains slashes get complex key and look for it // in mappings while (contextPath.indexOf(SLASH) != -1) { key = getKey(hostName, contextPath); log.trace("Check: {}", key); String globalName = mapping.get(key); if (globalName != null) { return getGlobal(globalName); } final int slashIndex = contextPath.lastIndexOf(SLASH); // Context path is substring from the beginning and till last slash // index contextPath = contextPath.substring(0, slashIndex); } // Get global scope key key = getKey(hostName, contextPath); log.trace("Check host and path: {}", key); // Look up for global scope switching keys if still not found String globalName = mapping.get(key); if (globalName != null) { return getGlobal(globalName); } key = getKey(EMPTY, contextPath); log.trace("Check wildcard host with path: {}", key); globalName = mapping.get(key); if (globalName != null) { return getGlobal(globalName); } key = getKey(hostName, EMPTY); log.trace("Check host with no path: {}", key); globalName = mapping.get(key); if (globalName != null) { return getGlobal(globalName); } key = getKey(EMPTY, EMPTY); log.trace("Check default host, default path: {}", key); return getGlobal(mapping.get(key)); } /** * Return global scope by name * * @param name Global scope name * @return Global scope */ public IGlobalScope getGlobal(String name) { if (name == null) { return null; } return globals.get(name); } /** * Register global scope * * @param scope Global scope to register */ public void registerGlobal(IGlobalScope scope) { log.trace("Registering global scope: {}", scope.getName(), scope); globals.put(scope.getName(), scope); } /** * Map key (host + / + context path) and global scope name * * @param hostName Host name * @param contextPath Context path * @param globalName Global scope name * @return true if mapping was added, false if already exist */ public boolean addMapping(String hostName, String contextPath, String globalName) { log.info("Add mapping global: {} host: {} context: {}", new Object[] { globalName, hostName, contextPath }); final String key = getKey(hostName, contextPath); log.debug("Add mapping: {} => {}", key, globalName); return (mapping.putIfAbsent(key, globalName) == null); } /** * Remove mapping with given key * * @param hostName Host name * @param contextPath Context path * @return true if mapping was removed, false if key doesn't exist */ public boolean removeMapping(String hostName, String contextPath) { log.info("Remove mapping host: {} context: {}", hostName, contextPath); final String key = getKey(hostName, contextPath); log.debug("Remove mapping: {}", key); return (mapping.remove(key) != null); } /** * Remove all mappings with given context path * * @param contextPath Context path * @return true if mapping was removed, false if key doesn't exist */ public boolean removeMapping(String contextPath) { log.info("Remove mapping context: {}", contextPath); final String key = getKey("", contextPath); log.debug("Remove mapping: {}", key); return (mapping.remove(key) != null); } /** * Return mapping * * @return Map of "scope key / scope name" pairs */ public Map<String, String> getMappingTable() { return mapping; } /** * Return global scope names set iterator * * @return Iterator */ public Iterator<String> getGlobalNames() { return globals.keySet().iterator(); } /** * Return global scopes set iterator * * @return Iterator */ public Iterator<IGlobalScope> getGlobalScopes() { return globals.values().iterator(); } /** * String representation of server * * @return String representation of server */ @Override public String toString() { return new ToStringCreator(this).append(mapping).toString(); } /** {@inheritDoc} */ public void addListener(IScopeListener listener) { scopeListeners.add(listener); } /** {@inheritDoc} */ public void addListener(IConnectionListener listener) { connectionListeners.add(listener); } /** {@inheritDoc} */ public void removeListener(IScopeListener listener) { scopeListeners.remove(listener); } /** {@inheritDoc} */ public void removeListener(IConnectionListener listener) { connectionListeners.remove(listener); } /** * Notify listeners about a newly created scope. * * @param scope * the scope that was created */ public void notifyScopeCreated(final IScope scope) { Runnable notification = new Runnable(){ public void run() { for (IScopeListener listener : scopeListeners) { listener.notifyScopeCreated(scope); } } }; notifier.execute(notification); } /** * Notify listeners that a scope was removed. * * @param scope * the scope that was removed */ public void notifyScopeRemoved(final IScope scope) { Runnable notification = new Runnable(){ public void run() { for (IScopeListener listener : scopeListeners) { listener.notifyScopeRemoved(scope); } } }; notifier.execute(notification); } /** * Notify listeners that a new connection was established. * * @param conn * the new connection */ public void notifyConnected(final IConnection conn) { Runnable notification = new Runnable(){ public void run() { for (IConnectionListener listener : connectionListeners) { listener.notifyConnected(conn); } } }; notifier.execute(notification); } /** * Notify listeners that a connection was disconnected. * * @param conn * the disconnected connection */ public void notifyDisconnected(final IConnection conn) { Runnable notification = new Runnable(){ public void run() { for (IConnectionListener listener : connectionListeners) { listener.notifyDisconnected(conn); } } }; notifier.execute(notification); } public int getNotifierThreadPoolSize() { return notifierThreadPoolSize; } public void setNotifierThreadPoolSize(int notifierThreadPoolSize) { this.notifierThreadPoolSize = notifierThreadPoolSize; } }