package org.red5.server;
/*
* RED5 Open Source Flash Server - http://www.osflash.org/red5
*
* Copyright (c) 2006-2009 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.File;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.management.ObjectName;
import org.red5.server.jmx.JMXAgent;
import org.red5.server.jmx.JMXFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
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;
/**
* Red5 applications loader
*/
public class ContextLoader implements ApplicationContextAware, ContextLoaderMBean {
/**
* Logger
*/
protected static Logger log = LoggerFactory.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<String, ApplicationContext> contextMap = new ConcurrentHashMap<String, ApplicationContext>();
/**
* @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;
}
/**
* Setter for context config name
*
* @param contextsConfig Context config name
*/
public void setContextsConfig(String contextsConfig) {
this.contextsConfig = contextsConfig;
}
/**
* Loads context settings from ResourceBundle (.properties file)
*
* @throws Exception I/O exception, casting exception and others
*/
public void init() throws Exception {
log.debug("ContextLoader init");
// register in jmx
//create a new mbean for this instance
oName = JMXFactory.createObjectName("type", "ContextLoader");
JMXAgent.registerMBean(this, this.getClass().getName(), ContextLoaderMBean.class, oName);
// Load properties bundle
Properties props = new Properties();
Resource res = applicationContext.getResource(contextsConfig);
if (!res.exists()) {
log.error("Contexts config must be set.");
return;
}
// Load properties file
props.load(res.getInputStream());
// 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);
// TODO: we should support arbitrary property substitution
config = config.replace("${red5.root}", System
.getProperty("red5.root"));
config = config.replace("${red5.config_root}", System
.getProperty("red5.config_root"));
log.info("Loading: {} = {}", name, config);
// Load context
loadContext(name, config);
}
}
public void uninit() {
log.debug("ContextLoader un-init");
JMXAgent.unregisterMBean(oName);
}
/**
* 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);
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);
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.isRunning()) {
log.debug("Context was running, attempting to stop");
ctx.stop();
}
ctx.close();
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);
}
}
}
context = null;
}
/**
* Return context by name
*
* @param name Context name
* @return Application context for given name
*/
public ApplicationContext getContext(String name) {
return contextMap.get(name);
}
/**
* Return parent context
*
* @return parent application context
*/
public ApplicationContext getParentContext() {
return parentContext;
}
public String getContextsConfig() {
return contextsConfig;
}
}