org.red5.server.tomcat.TomcatVHostLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.red5.server.tomcat.TomcatVHostLoader.java

Source

package org.red5.server.tomcat;

/*
 * 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.util.Map;

import javax.management.MBeanServer;
import javax.management.MBeanServerInvocationHandler;
import javax.management.ObjectName;
import javax.servlet.ServletContext;

import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Host;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Loader;
import org.apache.catalina.Valve;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardHost;
import org.red5.server.ContextLoader;
import org.red5.server.ContextLoaderMBean;
import org.red5.server.LoaderBase;
import org.red5.server.jmx.JMXAgent;
import org.red5.server.jmx.JMXFactory;
import org.red5.server.util.FileUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;

/**
 * Red5 loader for Tomcat virtual hosts.
 * 
 * @author Paul Gregoire (mondain@gmail.com)
 */
public class TomcatVHostLoader extends TomcatLoader implements TomcatVHostLoaderMBean {

    // Initialize Logging
    private static Logger log = LoggerFactory.getLogger(TomcatVHostLoader.class);

    /**
     * Base web applications directory
     */
    protected String webappRoot;

    //the virtual hosts name
    protected String name;
    //the domain
    protected String domain;

    protected boolean autoDeploy;
    protected boolean liveDeploy;
    protected boolean startChildren = true;
    protected boolean unpackWARs;

    /**
     * MBean object name used for de/registration purposes.
     */
    private ObjectName oName;

    private String defaultApplicationContextId = "default.context";

    /**
     * Initialization.
     */
    @SuppressWarnings("cast")
    public void init() {
        log.info("Loading tomcat virtual host");

        if (webappFolder != null) {
            //check for match with base webapp root
            if (webappFolder.equals(webappRoot)) {
                log.error("Web application root cannot be the same as base");
                return;
            }
        }

        ClassLoader classloader = Thread.currentThread().getContextClassLoader();

        //ensure we have a host
        if (host == null) {
            host = createHost();
        }

        host.setParentClassLoader(classloader);

        String propertyPrefix = name;
        if (domain != null) {
            propertyPrefix += '_' + domain.replace('.', '_');
        }
        log.debug("Generating name (for props) {}", propertyPrefix);
        System.setProperty(propertyPrefix + ".webapp.root", webappRoot);

        log.info("Virtual host root: {}", webappRoot);

        log.info("Virtual host context id: {}", defaultApplicationContextId);

        // Root applications directory
        File appDirBase = new File(webappRoot);
        // Subdirs of root apps dir
        File[] dirs = appDirBase.listFiles(new TomcatLoader.DirectoryFilter());
        // Search for additional context files
        for (File dir : dirs) {
            String dirName = '/' + dir.getName();
            // check to see if the directory is already mapped
            if (null == host.findChild(dirName)) {
                String webappContextDir = FileUtil.formatPath(appDirBase.getAbsolutePath(), dirName);
                Context ctx = null;
                if ("/root".equals(dirName) || "/root".equalsIgnoreCase(dirName)) {
                    log.debug("Adding ROOT context");
                    ctx = addContext("/", webappContextDir);
                } else {
                    log.debug("Adding context from directory scan: {}", dirName);
                    ctx = addContext(dirName, webappContextDir);
                }
                log.debug("Context: {}", ctx);
                webappContextDir = null;
            }
        }
        appDirBase = null;
        dirs = null;

        // Dump context list
        if (log.isDebugEnabled()) {
            for (Container cont : host.findChildren()) {
                log.debug("Context child name: {}", cont.getName());
            }
        }

        engine.addChild(host);

        // Start server
        try {
            log.info("Starting Tomcat virtual host");

            //may not have to do this step for every host
            LoaderBase.setApplicationLoader(new TomcatApplicationLoader(embedded, host, applicationContext));

            for (Container cont : host.findChildren()) {
                if (cont instanceof StandardContext) {
                    StandardContext ctx = (StandardContext) cont;

                    ServletContext servletContext = ctx.getServletContext();
                    log.debug("Context initialized: {}", servletContext.getContextPath());

                    //set the hosts id
                    servletContext.setAttribute("red5.host.id", getHostId());

                    String prefix = servletContext.getRealPath("/");
                    log.debug("Path: {}", prefix);

                    try {
                        Loader cldr = ctx.getLoader();
                        log.debug("Loader type: {}", cldr.getClass().getName());
                        ClassLoader webClassLoader = cldr.getClassLoader();
                        log.debug("Webapp classloader: {}", webClassLoader);
                        //create a spring web application context
                        XmlWebApplicationContext appctx = new XmlWebApplicationContext();
                        appctx.setClassLoader(webClassLoader);
                        appctx.setConfigLocations(new String[] { "/WEB-INF/red5-*.xml" });
                        //check for red5 context bean
                        if (applicationContext.containsBean(defaultApplicationContextId)) {
                            appctx.setParent(
                                    (ApplicationContext) applicationContext.getBean(defaultApplicationContextId));
                        } else {
                            log.warn("{} bean was not found in context: {}", defaultApplicationContextId,
                                    applicationContext.getDisplayName());
                            //lookup context loader and attempt to get what we need from it
                            if (applicationContext.containsBean("context.loader")) {
                                ContextLoader contextLoader = (ContextLoader) applicationContext
                                        .getBean("context.loader");
                                appctx.setParent(contextLoader.getContext(defaultApplicationContextId));
                            } else {
                                log.debug("Context loader was not found, trying JMX");
                                MBeanServer mbs = JMXFactory.getMBeanServer();
                                //get the ContextLoader from jmx
                                ObjectName oName = JMXFactory.createObjectName("type", "ContextLoader");
                                ContextLoaderMBean proxy = null;
                                if (mbs.isRegistered(oName)) {
                                    proxy = (ContextLoaderMBean) MBeanServerInvocationHandler.newProxyInstance(mbs,
                                            oName, ContextLoaderMBean.class, true);
                                    log.debug("Context loader was found");
                                    appctx.setParent(proxy.getContext(defaultApplicationContextId));
                                } else {
                                    log.warn("Context loader was not found");
                                }
                            }
                        }
                        if (log.isDebugEnabled()) {
                            if (appctx.getParent() != null) {
                                log.debug("Parent application context: {}", appctx.getParent().getDisplayName());
                            }
                        }
                        //
                        appctx.setServletContext(servletContext);
                        //set the root webapp ctx attr on the each servlet context so spring can find it later               
                        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
                                appctx);
                        appctx.refresh();
                    } catch (Throwable t) {
                        log.error("Error setting up context: {}", servletContext.getContextPath(), t);
                        if (log.isDebugEnabled()) {
                            t.printStackTrace();
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("Error loading Tomcat virtual host", e);
        }

    }

    /**
     * Un-initialization.
     */
    public void uninit() {
        log.debug("TomcatVHostLoader un-init");
        Container[] children = host.findChildren();
        for (Container c : children) {
            if (c instanceof StandardContext) {
                try {
                    ((StandardContext) c).stop();
                    host.removeChild(c);
                } catch (Exception e) {
                    log.error("Could not stop context: {}", c.getName(), e);
                }
            }
        }
        //remove system prop
        String propertyPrefix = name;
        if (domain != null) {
            propertyPrefix += '_' + domain.replace('.', '_');
        }
        System.clearProperty(propertyPrefix + ".webapp.root");
        //stop the host
        try {
            ((StandardHost) host).stop();
        } catch (LifecycleException e) {
            log.error("Could not stop host: {}", host.getName(), e);
        }
        //remove host
        engine.removeChild(host);
        //unregister jmx
        unregisterJMX();
    }

    /**
     * Starts a web application and its red5 (spring) component. This is basically a stripped down
     * version of init().
     * 
     * @return true on success
     */
    @SuppressWarnings("cast")
    public boolean startWebApplication(String applicationName) {
        boolean result = false;
        log.info("Starting Tomcat virtual host - Web application");

        log.info("Virtual host root: {}", webappRoot);

        log.info("Virtual host context id: {}", defaultApplicationContextId);

        // application directory
        String contextName = '/' + applicationName;

        Container cont = null;

        //check if the context already exists for the host
        if ((cont = host.findChild(contextName)) == null) {
            log.debug("Context did not exist in host");
            String webappContextDir = FileUtil.formatPath(webappRoot, applicationName);
            //prepend slash
            Context ctx = addContext(contextName, webappContextDir);
            //set the newly created context as the current container
            cont = ctx;
        } else {
            log.debug("Context already exists in host");
        }

        try {
            ServletContext servletContext = ((Context) cont).getServletContext();
            log.debug("Context initialized: {}", servletContext.getContextPath());

            String prefix = servletContext.getRealPath("/");
            log.debug("Path: {}", prefix);

            Loader cldr = cont.getLoader();
            log.debug("Loader type: {}", cldr.getClass().getName());
            ClassLoader webClassLoader = cldr.getClassLoader();
            log.debug("Webapp classloader: {}", webClassLoader);
            //create a spring web application context
            XmlWebApplicationContext appctx = new XmlWebApplicationContext();
            appctx.setClassLoader(webClassLoader);
            appctx.setConfigLocations(new String[] { "/WEB-INF/red5-*.xml" });
            //check for red5 context bean
            if (applicationContext.containsBean(defaultApplicationContextId)) {
                appctx.setParent((ApplicationContext) applicationContext.getBean(defaultApplicationContextId));
            } else {
                log.warn("{} bean was not found in context: {}", defaultApplicationContextId,
                        applicationContext.getDisplayName());
                //lookup context loader and attempt to get what we need from it
                if (applicationContext.containsBean("context.loader")) {
                    ContextLoader contextLoader = (ContextLoader) applicationContext.getBean("context.loader");
                    appctx.setParent(contextLoader.getContext(defaultApplicationContextId));
                } else {
                    log.debug("Context loader was not found, trying JMX");
                    MBeanServer mbs = JMXFactory.getMBeanServer();
                    //get the ContextLoader from jmx
                    ObjectName oName = JMXFactory.createObjectName("type", "ContextLoader");
                    ContextLoaderMBean proxy = null;
                    if (mbs.isRegistered(oName)) {
                        proxy = (ContextLoaderMBean) MBeanServerInvocationHandler.newProxyInstance(mbs, oName,
                                ContextLoaderMBean.class, true);
                        log.debug("Context loader was found");
                        appctx.setParent(proxy.getContext(defaultApplicationContextId));
                    } else {
                        log.warn("Context loader was not found");
                    }
                }
            }
            if (log.isDebugEnabled()) {
                if (appctx.getParent() != null) {
                    log.debug("Parent application context: {}", appctx.getParent().getDisplayName());
                }
            }
            //
            appctx.setServletContext(servletContext);
            //set the root webapp ctx attr on the each servlet context so spring can find it later               
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, appctx);
            appctx.refresh();

            result = true;
        } catch (Throwable t) {
            log.error("Error setting up context: {}", applicationName, t);
            if (log.isDebugEnabled()) {
                t.printStackTrace();
            }
        }

        return result;
    }

    /**
     * Create a standard host.
     * 
     * @return host
     */
    public Host createHost() {
        log.debug("Creating host");
        StandardHost stdHost = new StandardHost();
        stdHost.setAppBase(webappRoot);
        stdHost.setAutoDeploy(autoDeploy);
        if (domain == null) {
            stdHost.setName(name);
        } else {
            stdHost.setDomain(domain);
            //seems to require that the domain be appended to the name
            stdHost.setName(name + '.' + domain);
        }
        stdHost.setLiveDeploy(liveDeploy);
        //stdHost.setParent(container);
        stdHost.setStartChildren(startChildren);
        stdHost.setUnpackWARs(unpackWARs);
        //stdHost.setWorkDir(workDir);
        stdHost.setXmlNamespaceAware(false);
        stdHost.setXmlValidation(false);

        return stdHost;
    }

    /**
     * Returns the current host.
     *
     * @return host
     */
    public Host getHost() {
        return host;
    }

    /**
     * Adds an alias to the current host.
     * 
     * @param alias alias
     */
    public void addAlias(String alias) {
        log.debug("Adding alias: {}", alias);
        host.addAlias(alias);
    }

    /**
     * Removes an alias from the current host.
     * 
     * @param alias Alias
     */
    public void removeAlias(String alias) {
        log.debug("Removing alias: {}", alias);
        String[] aliases = host.findAliases();
        for (String s : aliases) {
            if (alias.equals(s)) {
                host.removeAlias(alias);
                break;
            }
        }
    }

    /**
     * Adds a valve to the current host.
     * 
     * @param valve Valve
     */
    public void addValve(Valve valve) {
        log.debug("Adding valve: {}", valve);
        log.debug("Valve info: {}", valve.getInfo());
        ((StandardHost) host).addValve(valve);
    }

    /**
     * Removes a valve from the current host.
     * 
     * @param valveInfo Valve Information.
     */
    public void removeValve(String valveInfo) {
        log.debug("Removing valve: {}", valveInfo);
        try {
            String[] valveNames = ((StandardHost) host).getValveNames();
            for (String s : valveNames) {
                log.debug("Valve name: {}", s);
            }
        } catch (Exception e) {
            log.error("", e);
        }

        Valve[] valves = ((StandardHost) host).getValves();
        for (Valve v : valves) {
            log.debug("Valve: {}", v);
            log.debug("Valve info: {}", v.getInfo());
        }

        //TODO: fix removing valves
        //((StandardHost) host).removeValve(valve);   
    }

    /**
     * Set additional contexts.
     * 
     * @param contexts Map of contexts
     */
    @Override
    public void setContexts(Map<String, String> contexts) {
        log.debug("setContexts: {}", contexts.size());
        for (Map.Entry<String, String> entry : contexts.entrySet()) {
            host.addChild(embedded.createContext(entry.getKey(), webappRoot + entry.getValue()));
        }
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDomain() {
        return domain;
    }

    public void setDomain(String domain) {
        this.domain = domain;
    }

    public String getWebappRoot() {
        return webappRoot;
    }

    public void setWebappRoot(String webappRoot) {
        this.webappRoot = webappRoot;
    }

    public boolean getAutoDeploy() {
        return autoDeploy;
    }

    public void setAutoDeploy(boolean autoDeploy) {
        this.autoDeploy = autoDeploy;
    }

    public boolean getLiveDeploy() {
        return liveDeploy;
    }

    public void setLiveDeploy(boolean liveDeploy) {
        this.liveDeploy = liveDeploy;
    }

    public boolean getStartChildren() {
        return startChildren;
    }

    public void setStartChildren(boolean startChildren) {
        this.startChildren = startChildren;
    }

    public boolean getUnpackWARs() {
        return unpackWARs;
    }

    public void setUnpackWARs(boolean unpackWARs) {
        this.unpackWARs = unpackWARs;
    }

    public String getDefaultApplicationContextId() {
        return defaultApplicationContextId;
    }

    public void setDefaultApplicationContextId(String defaultApplicationContextId) {
        this.defaultApplicationContextId = defaultApplicationContextId;
    }

    public void registerJMX() {
        oName = JMXFactory.createObjectName("type", "TomcatVHostLoader", "name", name, "domain", domain);
        JMXAgent.registerMBean(this, this.getClass().getName(), TomcatVHostLoaderMBean.class, oName);
    }

    public void unregisterJMX() {
        JMXAgent.unregisterMBean(oName);
    }

}