org.red5.server.ContextLoader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ant-media-server Show documentation
Show all versions of ant-media-server Show documentation
Ant Media Server supports RTMP, RTSP, MP4, HLS, WebRTC, Adaptive Streaming, etc.
/*
* RED5 Open Source Media Server - https://github.com/Red5/
*
* Copyright 2006-2016 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.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.StandardMBean;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.jmx.mxbeans.ContextLoaderMXBean;
import org.slf4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.web.context.support.XmlWebApplicationContext;
/**
* Red5 applications loader
*
* @author The Red5 Project
* @author Tiago Jacobs ([email protected])
* @author Paul Gregoire ([email protected])
*/
@ManagedResource(objectName = "org.red5.server:name=contextLoader,type=ContextLoader", description = "ContextLoader")
public class ContextLoader implements ApplicationContextAware, InitializingBean, DisposableBean, ContextLoaderMXBean {
protected static Logger log = Red5LoggerFactory.getLogger(ContextLoader.class);
/**
* Spring Application context
*/
protected ApplicationContext applicationContext;
/**
* Spring parent app context
*/
protected ApplicationContext parentContext;
/**
* Context location files
*/
protected String contextsConfig;
/**
* MBean object name used for de/registration purposes.
*/
private ObjectName oName;
/**
* Context map
*/
protected ConcurrentMap contextMap;
/**
* Registers with JMX and registers a shutdown hook.
*
* @throws Exception
* I/O exception, casting exception and others
*/
public void afterPropertiesSet() throws Exception {
log.info("ContextLoader init");
// register in jmx
registerJMX();
// initialize
init();
}
/**
* Un-loads or un-initializes the contexts; this is a shutdown method for this loader.
*/
public void destroy() throws Exception {
log.info("ContextLoader un-init");
shutdown();
}
/**
* Loads context settings from ResourceBundle (.properties file)
*/
public void init() throws IOException {
// Load properties bundle
Properties props = new Properties();
Resource res = applicationContext.getResource(contextsConfig);
if (res.exists()) {
// Load properties file
props.load(res.getInputStream());
// Pattern for arbitrary property substitution
Pattern patt = Pattern.compile("\\$\\{([^\\}]+)\\}");
Matcher matcher = null;
// Iterate thru properties keys and replace config attributes with
// system attributes
for (Object key : props.keySet()) {
String name = (String) key;
String config = props.getProperty(name);
String configReplaced = config + "";
//
matcher = patt.matcher(config);
//execute the regex
while (matcher.find()) {
String sysProp = matcher.group(1);
String systemPropValue = System.getProperty(sysProp);
if (systemPropValue == null) {
systemPropValue = "null";
}
configReplaced = configReplaced.replace(String.format("${%s}", sysProp), systemPropValue);
}
log.info("Loading: {} = {} => {}", new Object[] { name, config, configReplaced });
matcher.reset();
// Load context
loadContext(name, configReplaced);
}
patt = null;
matcher = null;
} else {
log.error("Contexts config must be set");
}
}
/**
* Loads a context (Red5 application) and stores it in a context map, then adds it's beans to parent (that is, Red5)
*
* @param name
* Context name
* @param config
* Filename
*/
public void loadContext(String name, String config) {
log.debug("Load context - name: {} config: {}", name, config);
// check the existence of the config file
try {
File configFile = new File(config);
if (!configFile.exists()) {
log.warn("Config file was not found at: {}", configFile.getCanonicalPath());
configFile = new File("file://" + config);
if (!configFile.exists()) {
log.warn("Config file was not found at either: {}", configFile.getCanonicalPath());
} else {
config = "file://" + config;
}
}
} catch (IOException e) {
log.error("Error looking for config file", e);
}
// add the context to the parent, this will be red5.xml
ConfigurableBeanFactory factory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
if (factory.containsSingleton(name)) {
log.warn("Singleton {} already exists, try unload first", name);
return;
}
// if parent context was not set then lookup red5.common
if (parentContext == null) {
log.debug("Lookup common - bean:{} local:{} singleton:{}", new Object[] { factory.containsBean("red5.common"), factory.containsLocalBean("red5.common"), factory.containsSingleton("red5.common"), });
parentContext = (ApplicationContext) factory.getBean("red5.common");
}
if (config.startsWith("/")) {
// Spring always interprets files as relative, so will strip a leading slash unless we tell
// it otherwise. It also appears to not need this for Windows
// absolute paths (e.g. C:\Foo\Bar) so we don't catch that either
String newConfig = "file://" + config;
log.debug("Resetting {} to {}", config, newConfig);
config = newConfig;
}
ApplicationContext context = new FileSystemXmlApplicationContext(new String[] { config }, parentContext);
log.debug("Adding to context map - name: {} context: {}", name, context);
if (contextMap == null) {
contextMap = new ConcurrentHashMap<>(3, 0.9f, 1);
}
contextMap.put(name, context);
// Register context in parent bean factory
log.debug("Registering - name: {}", name);
factory.registerSingleton(name, context);
}
/**
* Unloads a context (Red5 application) and removes it from the context map, then removes it's beans from the parent (that is, Red5)
*
* @param name
* Context name
*/
public void unloadContext(String name) {
log.debug("Un-load context - name: {}", name);
ApplicationContext context = contextMap.remove(name);
log.debug("Context from map: {}", context);
String[] bnames = BeanFactoryUtils.beanNamesIncludingAncestors(context);
for (String bname : bnames) {
log.debug("Bean: {}", bname);
}
ConfigurableBeanFactory factory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
if (factory.containsSingleton(name)) {
log.debug("Context found in parent, destroying: {}", name);
FileSystemXmlApplicationContext ctx = (FileSystemXmlApplicationContext) factory.getSingleton(name);
if (ctx != null)
{
if (ctx.isRunning()) {
log.debug("Context was running, attempting to stop");
ctx.stop();
}
if (ctx.isActive()) {
log.debug("Context is active, attempting to close");
ctx.close();
} else {
try {
factory.destroyBean(name, ctx);
} catch (Exception e) {
log.warn("Context destroy failed for: {}", name, e);
} finally {
if (factory.containsSingleton(name)) {
log.debug("Singleton still exists, trying another destroy method");
((DefaultListableBeanFactory) factory).destroySingleton(name);
}
}
}
}
} else {
log.debug("Context does not contain singleton: {}", name);
}
context = null;
}
/**
* Shut server down.
*/
public void shutdown() {
log.info("Shutting down");
if (contextMap != null) {
log.debug("Context map: {}", contextMap);
try {
// unload all the contexts in the map
for (Map.Entry entry : contextMap.entrySet()) {
String contextName = entry.getKey();
log.info("Unloading context {} on shutdown", contextName);
unloadContext(contextName);
}
contextMap.clear();
} catch (Exception e) {
log.warn("Exception shutting down contexts", e);
} finally {
contextMap = null;
}
}
unregisterJMX();
log.info("Shutdown complete");
}
/**
* Return context by name
*
* @param name
* Context name
* @return Application context for given name
*/
public ApplicationContext getContext(String name) {
if (contextMap != null) {
return contextMap.get(name);
} else {
return null;
}
}
/**
* Sets a parent context for child context based on a given key.
*
* @param parentContextKey
* key for the parent context
* @param appContextId
* id of the child context
*/
public void setParentContext(String parentContextKey, String appContextId) {
log.debug("Set parent context {} on {}", parentContextKey, appContextId);
ApplicationContext parentContext = getContext(parentContextKey);
if (parentContext != null) {
XmlWebApplicationContext childContext = (XmlWebApplicationContext) getContext(appContextId);
if (childContext != null) {
childContext.setParent(parentContext);
} else {
log.debug("Child context not found");
}
} else {
log.debug("Parent context not found");
}
}
protected void registerJMX() {
// register with jmx
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
try {
oName = new ObjectName("org.red5.server:name=contextLoader,type=ContextLoader");
// check for existing registration before registering
if (!mbs.isRegistered(oName)) {
mbs.registerMBean(new StandardMBean(this, ContextLoaderMXBean.class, true), oName);
} else {
log.debug("ContextLoader is already registered in JMX");
}
} catch (Exception e) {
log.warn("Error on jmx registration", e);
}
}
protected void unregisterJMX() {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
try {
mbs.unregisterMBean(oName);
} catch (Exception e) {
log.warn("Exception unregistering: {}", oName, e);
}
oName = null;
}
/**
* @param applicationContext
* Spring application context
* @throws BeansException
* Top level exception for app context (that is, in fact, beans factory)
*/
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* Setter for parent application context
*
* @param parentContext
* Parent Spring application context
*/
public void setParentContext(ApplicationContext parentContext) {
this.parentContext = parentContext;
}
/**
* Return parent context
*
* @return parent application context
*/
public ApplicationContext getParentContext() {
return parentContext;
}
/**
* Setter for context config name
*
* @param contextsConfig
* Context config name
*/
public void setContextsConfig(String contextsConfig) {
this.contextsConfig = contextsConfig;
}
public String getContextsConfig() {
return contextsConfig;
}
}