All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.red5.server.tomcat.TomcatLoader Maven / Gradle / Ivy

The newest version!
/*
 * RED5 Open Source Flash 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.tomcat;

import java.io.File;
import java.io.FilenameFilter;
import java.lang.management.ManagementFactory;
import java.net.BindException;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.management.JMX;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.security.auth.message.config.AuthConfigFactory;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.catalina.Cluster;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Loader;
import org.apache.catalina.Realm;
import org.apache.catalina.Valve;
import org.apache.catalina.authenticator.jaspic.AuthConfigFactoryImpl;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.ContainerBase;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.realm.JAASRealm;
import org.apache.catalina.realm.NullRealm;
import org.apache.catalina.realm.RealmBase;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.ContextLoader;
import org.red5.server.LoaderBase;
import org.red5.server.api.IApplicationContext;
import org.red5.server.jmx.mxbeans.ContextLoaderMXBean;
import org.red5.server.jmx.mxbeans.LoaderMXBean;
import org.red5.server.security.IRed5Realm;
import org.red5.server.util.FileUtil;
import org.slf4j.Logger;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Red5 loader for Tomcat.
 * 
 * http://tomcat.apache.org/tomcat-8.5-doc/api/index.html
 * 
 * @author Paul Gregoire ([email protected])
 */
@ManagedResource(objectName = "org.red5.server:type=TomcatLoader", description = "TomcatLoader")
public class TomcatLoader extends LoaderBase implements InitializingBean, DisposableBean, LoaderMXBean {

    static {
        // set jaspic AuthConfigFactory to prevent NPEs like this:
        // java.lang.NullPointerException
        //     at org.apache.catalina.authenticator.AuthenticatorBase.getJaspicProvider(AuthenticatorBase.java:1140)
        //     at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:431)
        //     at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
        Security.setProperty(AuthConfigFactory.DEFAULT_FACTORY_SECURITY_PROPERTY, AuthConfigFactoryImpl.class.getName());
    }

    /**
     * Filters directory content
     */
    protected final static class DirectoryFilter implements FilenameFilter {
        /**
         * Check whether file matches filter rules
         * 
         * @param dir
         *            Directory
         * @param name
         *            File name
         * @return true If file does match filter rules, false otherwise
         */
        public boolean accept(File dir, String name) {
            File f = new File(dir, name);
            if (log.isTraceEnabled()) {
                log.trace("Filtering: {} name: {} dir: {}", dir.getName(), name, f.getAbsolutePath());
            }
            // filter out all non-directories that are hidden and/or not readable
            boolean result = f.isDirectory() && f.canRead() && !f.isHidden();
            // nullify
            f = null;
            return result;
        }
    }

    // Initialize Logging
    private static Logger log = Red5LoggerFactory.getLogger(TomcatLoader.class);

    public static final String defaultSpringConfigLocation = "/WEB-INF/red5-*.xml";

    public static final String defaultParentContextKey = "default.context";

    static {
        log.debug("Initializing Tomcat");
    }

    /**
     * Common name for the Service and Engine components.
     */
    public String serviceEngineName = "red5Engine";

    /**
     * Base container host.
     */
    protected Host host;

    /**
     * Embedded Tomcat service (like Catalina).
     */
    protected static EmbeddedTomcat embedded;

    /**
     * Tomcat engine.
     */
    protected static Engine engine;

    /**
     * Tomcat realm.
     */
    protected Realm realm;

    /**
     * Hosts
     */
    protected List hosts;

    /**
     * Connectors
     */
    protected List connectors;
    
    
    /**
     * Cluster
     */
    @Autowired(required=false)
    private Cluster cluster;

    /**
     * Valves
     */
    protected List valves = new ArrayList<>();

    @Override
    public void afterPropertiesSet() throws Exception {
        start();
    }

    /**
     * Add context for path and docbase to current host.
     * 
     * @param contextPath
     *            Path
     * @param docBase
     *            Document base
     * @return Catalina context (that is, web application)
     * @throws ServletException
     */
    public Context addContext(String path, String docBase) throws ServletException {
        return addContext(path, docBase, host);
    }

    /**
     * Add context for path and docbase to a host.
     * 
     * @param contextPath
     *            Path
     * @param docBase
     *            Document base
     * @param host
     *            Host to add context to
     * @return Catalina context (that is, web application)
     * @throws ServletException
     */
    public Context addContext(String contextPath, String docBase, Host host) throws ServletException {
        log.debug("Add context - path: {} docbase: {}", contextPath, docBase);
        // instance a context
        org.apache.catalina.Context ctx = embedded.addWebapp(host, contextPath, docBase);
        if (ctx != null) {
            // grab the current classloader
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            ctx.setParentClassLoader(classLoader);
            // get the associated loader for the context
            Object ldr = ctx.getLoader();
            log.trace("Context loader (null if the context has not been started): {}", ldr);
            if (ldr == null) {
                WebappLoader wldr = new WebappLoader(classLoader);
                // add the Loader to the context
                ctx.setLoader(wldr);
            }
            log.trace("Context loader (check): {} Context classloader: {}", ctx.getLoader(), ctx.getLoader().getClassLoader());
            LoaderBase.setRed5ApplicationContext(getHostId() + contextPath, new TomcatApplicationContext(ctx));
        } else {
            log.trace("Context is null");
        }
        return ctx;
    }

    /**
     * Remove context from the current host.
     * 
     * @param path
     *            Path
     */
    @Override
    public void removeContext(String path) {
        Container[] children = host.findChildren();
        for (Container c : children) {
            if (c instanceof StandardContext && c.getName().equals(path)) {
                try {
                    ((StandardContext) c).stop();
                    host.removeChild(c);
                    break;
                } catch (Exception e) {
                    log.error("Could not remove context: {}", c.getName(), e);
                }
            }
        }
        IApplicationContext ctx = LoaderBase.removeRed5ApplicationContext(path);
        if (ctx != null) {
            ctx.stop();
        } else {
            log.warn("Context could not be stopped, it was null for path: {}", path);
        }
    }

    /**
     * Initialization.
     */
    public void start() throws ServletException {
        log.info("Loading Tomcat");
        //get a reference to the current threads classloader
        final ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
        // root location for servlet container
        String serverRoot = System.getProperty("red5.root");
        log.info("Server root: {}", serverRoot);
        String confRoot = System.getProperty("red5.config_root");
        log.info("Config root: {}", confRoot);
        // check naming flag
        Boolean useNaming = Boolean.valueOf(System.getProperty("catalina.useNaming"));
        // create one embedded (server) and use it everywhere
        if (embedded == null) {
            embedded = new EmbeddedTomcat();
        }
        File serverRootF = new File(serverRoot);
        embedded.getServer().setCatalinaBase(serverRootF);
        embedded.getServer().setCatalinaHome(serverRootF);
        embedded.setHost(host);
        // provide default configuration for a context. This is the programmatic equivalent of the default web.xml
        // default-web.xml
        //embedded.initWebappDefaults(confRoot);
        // controls if the loggers will be silenced or not
        embedded.setSilent(false);
        // get the engine
        engine = embedded.getEngine();
        // give the engine a name
        engine.setName(serviceEngineName);
        // set the default host for our engine
        engine.setDefaultHost(host.getName());
        
        if (cluster != null) {
        	engine.setCluster(cluster);
        }
        // set the webapp folder if not already specified
        if (webappFolder == null) {
            // Use default webapps directory
            webappFolder = FileUtil.formatPath(System.getProperty("red5.root"), "/webapps");
        }
        System.setProperty("red5.webapp.root", webappFolder);
        log.info("Application root: {}", webappFolder);
        // Root applications directory
        File appDirBase = new File(webappFolder);
        // Subdirs of root apps dir
        File[] dirs = appDirBase.listFiles(new 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);
                log.debug("Webapp context directory (full path): {}", webappContextDir);
                Context ctx = null;
                if ("/root".equals(dirName) || "/root".equalsIgnoreCase(dirName)) {
                    log.trace("Adding ROOT context");
                    ctx = addContext("", webappContextDir);
                } else {
                    log.trace("Adding context from directory scan: {}", dirName);
                    ctx = addContext(dirName, webappContextDir);
                }
                log.trace("Context: {}", ctx);
                //see if the application requests php support
                String enablePhp = ctx.findParameter("enable-php");
                //if its null try to read directly
                if (enablePhp == null) {
                    File webxml = new File(webappContextDir + "/WEB-INF/", "web.xml");
                    if (webxml.exists() && webxml.canRead()) {
                        try {
                            DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
                            DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
                            Document doc = docBuilder.parse(webxml);
                            // normalize text representation
                            doc.getDocumentElement().normalize();
                            log.trace("Root element of the doc is {}", doc.getDocumentElement().getNodeName());
                            NodeList listOfElements = doc.getElementsByTagName("context-param");
                            int totalElements = listOfElements.getLength();
                            log.trace("Total no of elements: {}", totalElements);
                            for (int s = 0; s < totalElements; s++) {
                                Node fstNode = listOfElements.item(s);
                                if (fstNode.getNodeType() == Node.ELEMENT_NODE) {
                                    Element fstElmnt = (Element) fstNode;
                                    NodeList fstNmElmntLst = fstElmnt.getElementsByTagName("param-name");
                                    Element fstNmElmnt = (Element) fstNmElmntLst.item(0);
                                    NodeList fstNm = fstNmElmnt.getChildNodes();
                                    String pName = (fstNm.item(0)).getNodeValue();
                                    log.trace("Param name: {}", pName);
                                    if ("enable-php".equals(pName)) {
                                        NodeList lstNmElmntLst = fstElmnt.getElementsByTagName("param-value");
                                        Element lstNmElmnt = (Element) lstNmElmntLst.item(0);
                                        NodeList lstNm = lstNmElmnt.getChildNodes();
                                        String pValue = (lstNm.item(0)).getNodeValue();
                                        log.trace("Param value: {}", pValue);
                                        enablePhp = pValue;
                                        //
                                        break;
                                    }
                                }
                            }
                        } catch (Exception e) {
                            log.warn("Error reading web.xml", e);
                        }
                    }
                    webxml = null;
                }
                log.debug("Enable php: {}", enablePhp);
                if ("true".equals(enablePhp)) {
                    log.info("Adding PHP (Quercus) servlet for context: {}", ctx.getName());
                    // add servlet wrapper
                    StandardWrapper wrapper = (StandardWrapper) ctx.createWrapper();
                    wrapper.setServletName("QuercusServlet");
                    wrapper.setServletClass("com.caucho.quercus.servlet.QuercusServlet");
                    log.debug("Wrapper: {}", wrapper);
                    ctx.addChild(wrapper);
                    // add servlet mappings
                    ctx.addServletMappingDecoded("*.php", "QuercusServlet");
                }
                webappContextDir = null;
            }
        }
        appDirBase = null;
        dirs = null;
        // Dump context list
        if (log.isDebugEnabled()) {
            for (Container cont : host.findChildren()) {
                log.debug("Context child name: {}", cont.getName());
            }
        }
        // set a realm on the "server" if specified
        if (realm != null) {
            embedded.getEngine().setRealm(realm);
        } else {
            realm = new NullRealm();
            embedded.getEngine().setRealm(realm);
        }
        // use Tomcat jndi or not
        if (Boolean.TRUE.equals(useNaming)) {
            embedded.enableNaming();
        }
        // add the valves to the host
        for (Valve valve : valves) {
            log.debug("Adding host valve: {}", valve);
            ((StandardHost) host).addValve(valve);
        }
        // add any additional hosts
        if (hosts != null && !hosts.isEmpty()) {
            // grab current contexts from base host
            Container[] currentContexts = host.findChildren();
            log.info("Adding {} additional hosts", hosts.size());
            for (Host h : hosts) {
                log.debug("Host - name: {} appBase: {} info: {}", new Object[] { h.getName(), h.getAppBase(), h });
                //add the contexts to each host
                for (Container cont : currentContexts) {
                    Context c = (Context) cont;
                    addContext(c.getPath(), c.getDocBase(), h);
                }
                //add the host to the engine
                engine.addChild(h);
            }
        }
        try {
            // loop through connectors and apply methods / props
            boolean added = false;
            for (TomcatConnector tomcatConnector : connectors) {
                // get the connector
                Connector connector = tomcatConnector.getConnector();
                // add new Connector to set of Connectors for embedded server, associated with Engine
                if (!added) {
                    embedded.setConnector(connector);
                    added = true;
                } else {
                    embedded.getService().addConnector(connector);
                }
                log.trace("Connector oName: {}", connector.getObjectName());
            }
        } catch (Exception ex) {
            log.warn("An exception occurred during network configuration", ex);
        }
        // create an executor for "ordered" start-up of the webapps
        ExecutorService executor = Executors.newSingleThreadExecutor();
        try {
            log.info("Starting Tomcat servlet engine");
            embedded.start();
            // create references for later lookup
            LoaderBase.setApplicationLoader(new TomcatApplicationLoader(embedded, host, applicationContext));
            for (final Container cont : host.findChildren()) {
                if (cont instanceof StandardContext) {
                    if (log.isDebugEnabled()) {
                        ContainerBase cb = (ContainerBase) cont;
                        log.debug("Oname - domain: {}", cb.getDomain());
                    }
                    final StandardContext ctx = (StandardContext) cont;
                    final ServletContext servletContext = ctx.getServletContext();
                    // set the hosts id
                    servletContext.setAttribute("red5.host.id", getHostId());
                    final String prefix = servletContext.getRealPath("/");
                    log.info("Context initialized: {} path: {}", servletContext.getContextPath(), prefix);
                    try {
                        ctx.resourcesStart();
                        log.debug("Context - privileged: {}, start time: {}, reloadable: {}", new Object[] { ctx.getPrivileged(), ctx.getStartTime(), ctx.getReloadable() });
                        Loader cldr = ctx.getLoader();
                        log.debug("Loader delegate: {} type: {}", cldr.getDelegate(), cldr.getClass().getName());
                        if (log.isTraceEnabled()) {
                            if (cldr instanceof WebappLoader) {
                                log.trace("WebappLoader class path: {}", ((WebappLoader) cldr).getClasspath());
                            }
                        }
                        final ClassLoader webClassLoader = cldr.getClassLoader();
                        log.debug("Webapp classloader: {}", webClassLoader);
                        // get the (spring) config file path
                        final String contextConfigLocation = servletContext.getInitParameter(org.springframework.web.context.ContextLoader.CONFIG_LOCATION_PARAM) == null ? defaultSpringConfigLocation : servletContext.getInitParameter(org.springframework.web.context.ContextLoader.CONFIG_LOCATION_PARAM);
                        log.debug("Spring context config location: {}", contextConfigLocation);
                        // get the (spring) parent context key
                        final String parentContextKey = servletContext.getInitParameter(org.springframework.web.context.ContextLoader.LOCATOR_FACTORY_KEY_PARAM) == null ? defaultParentContextKey : servletContext.getInitParameter(org.springframework.web.context.ContextLoader.LOCATOR_FACTORY_KEY_PARAM);
                        log.debug("Spring parent context key: {}", parentContextKey);
                        // set current threads classloader to the webapp classloader
                        Thread.currentThread().setContextClassLoader(webClassLoader);
                        // create a thread to speed-up application loading
                        Future appStartTask = executor.submit(new Runnable() {
                            public void run() {
                                //set thread context classloader to web classloader
                                Thread.currentThread().setContextClassLoader(webClassLoader);
                                Thread.currentThread().setName("Loader:" + servletContext.getContextPath());
                                //get the web app's parent context
                                ApplicationContext parentContext = null;
                                if (applicationContext.containsBean(parentContextKey)) {
                                    parentContext = (ApplicationContext) applicationContext.getBean(parentContextKey);
                                } else {
                                    log.warn("Parent context was not found: {}", parentContextKey);
                                }
                                // create a spring web application context
                                final String contextClass = servletContext.getInitParameter(org.springframework.web.context.ContextLoader.CONTEXT_CLASS_PARAM) == null ? XmlWebApplicationContext.class.getName() : servletContext.getInitParameter(org.springframework.web.context.ContextLoader.CONTEXT_CLASS_PARAM);
                                // web app context (spring)
                                ConfigurableWebApplicationContext appctx = null;
                                try {
                                    Class clazz = Class.forName(contextClass, true, webClassLoader);
                                    appctx = (ConfigurableWebApplicationContext) clazz.newInstance();
                                    // 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.setConfigLocations(new String[] { contextConfigLocation });
                                    appctx.setServletContext(servletContext);
                                    // set parent context or use current app context
                                    if (parentContext != null) {
                                        appctx.setParent(parentContext);
                                    } else {
                                        appctx.setParent(applicationContext);
                                    }
                                    // refresh the factory
                                    log.trace("Classloader prior to refresh: {}", appctx.getClassLoader());
                                    appctx.refresh();
                                    if (log.isDebugEnabled()) {
                                        log.debug("Red5 app is active: {} running: {}", appctx.isActive(), appctx.isRunning());
                                    }
                                    // set a realm for the webapp if one is specified
                                    if (appctx.containsBean("realm")) {
                                        log.debug("Realm specified in context configuration");
                                        Realm contextRealm = (Realm) appctx.getBean("realm");
                                        if (contextRealm != null) {
                                            log.debug("Realm class: {}", contextRealm.getClass().getName());
                                            contextRealm.setContainer(cont);
                                            ctx.setRealm(contextRealm);
                                            // when a realm implements our red5 realm, add the app and servlet contexts
                                            if (contextRealm instanceof IRed5Realm) {
                                                ((IRed5Realm) contextRealm).setApplicationContext(appctx);
                                                ((IRed5Realm) contextRealm).setServletContext(servletContext);
                                            }
                                            // set the system property to allow the config to be located
                                            if (contextRealm instanceof JAASRealm) {
                                                log.debug("Realm is JAAS type");
                                                // this may interfere with other concurrently loaded jaas realms
                                                System.setProperty("java.security.auth.login.config", prefix + "WEB-INF/jaas.config");
                                            }
                                            log.debug("Realm info: {} path: {}", contextRealm, ((RealmBase) contextRealm).getRealmPath());
                                        }
                                    }
                                    appctx.start();
                                } catch (Throwable e) {
                                		log.error(ExceptionUtils.getStackTrace(e));
                                    throw new RuntimeException("Failed to load webapplication context class", e);
                                }
                            }
                        });
                        // see if everything completed
                        log.debug("Context: {} done: {}", servletContext.getContextPath(), appStartTask.isDone());
                    } catch (Throwable t) {
                        log.error("Error setting up context: {} due to: {}", servletContext.getContextPath(), t.getMessage());
                        t.printStackTrace();
                    } finally {
                        //reset the classloader
                        Thread.currentThread().setContextClassLoader(originalClassLoader);
                    }
                }
            }
            // if everything is ok at this point then call the rtmpt and rtmps beans so they will init
            if (applicationContext.containsBean("rtmpt.server")) {
                log.debug("Initializing RTMPT");
                applicationContext.getBean("rtmpt.server");
                log.debug("Finished initializing RTMPT");
            } else {
                log.info("Dedicated RTMPT server configuration was not specified");
            }
            if (applicationContext.containsBean("rtmps.server")) {
                log.debug("Initializing RTMPS");
                applicationContext.getBean("rtmps.server");
                log.debug("Finished initializing RTMPS");
            } else {
                log.debug("Dedicated RTMPS server configuration was not specified");
            }
        } catch (Exception e) {
            if (e instanceof BindException || e.getMessage().indexOf("BindException") != -1) {
                log.error("Error loading tomcat, unable to bind connector. You may not have permission to use the selected port", e);
            } else {
                log.error("Error loading tomcat", e);
            }
        } finally {
            // finish-up with the executor
            executor.shutdown();
            // do our jmx stuff
            registerJMX();
        }
        log.debug("Tomcat load completed");
    }

    /**
     * Starts a web application and its red5 (spring) component. This is basically a stripped down version of start().
     * 
     * @return true on success
     * @throws ServletException
     */
    public boolean startWebApplication(String applicationName) throws ServletException {
        log.info("Starting Tomcat - Web application");
        boolean result = false;
        //get a reference to the current threads classloader
        final ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
        log.debug("Webapp root: {}", webappFolder);
        if (webappFolder == null) {
            // Use default webapps directory
            webappFolder = System.getProperty("red5.root") + "/webapps";
        }
        System.setProperty("red5.webapp.root", webappFolder);
        log.info("Application root: {}", webappFolder);
        // application directory
        String contextName = '/' + applicationName;
        Container ctx = null;
        // Root applications directory
        File appDirBase = new File(webappFolder);
        // check if the context already exists for the host
        if ((ctx = host.findChild(contextName)) == null) {
            log.debug("Context did not exist in host");
            String webappContextDir = FileUtil.formatPath(appDirBase.getAbsolutePath(), applicationName);
            log.debug("Webapp context directory (full path): {}", webappContextDir);
            // set the newly created context as the current container
            ctx = addContext(contextName, webappContextDir);
        } else {
            log.debug("Context already exists in host");
        }
        final ServletContext servletContext = ((Context) ctx).getServletContext();
        log.debug("Context initialized: {}", servletContext.getContextPath());
        String prefix = servletContext.getRealPath("/");
        log.debug("Path: {}", prefix);
        try {
            Loader cldr = ((Context) ctx).getLoader();
            log.debug("Loader delegate: {} type: {}", cldr.getDelegate(), cldr.getClass().getName());
            if (cldr instanceof WebappLoader) {
                log.debug("WebappLoader class path: {}", ((WebappLoader) cldr).getClasspath());
            }
            final ClassLoader webClassLoader = cldr.getClassLoader();
            log.debug("Webapp classloader: {}", webClassLoader);
            // get the (spring) config file path
            final String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation") == null ? defaultSpringConfigLocation : servletContext.getInitParameter("contextConfigLocation");
            log.debug("Spring context config location: {}", contextConfigLocation);
            // get the (spring) parent context key
            final String parentContextKey = servletContext.getInitParameter("parentContextKey") == null ? defaultParentContextKey : servletContext.getInitParameter("parentContextKey");
            log.debug("Spring parent context key: {}", parentContextKey);
            //set current threads classloader to the webapp classloader
            Thread.currentThread().setContextClassLoader(webClassLoader);
            //create a thread to speed-up application loading
            Thread thread = new Thread("Launcher:" + servletContext.getContextPath()) {
                @SuppressWarnings("cast")
                public void run() {
                    //set current threads classloader to the webapp classloader
                    Thread.currentThread().setContextClassLoader(webClassLoader);
                    // create a spring web application context
                    XmlWebApplicationContext appctx = new XmlWebApplicationContext();
                    appctx.setClassLoader(webClassLoader);
                    appctx.setConfigLocations(new String[] { contextConfigLocation });
                    // check for red5 context bean
                    ApplicationContext parentAppCtx = null;
                    if (applicationContext.containsBean(defaultParentContextKey)) {
                        parentAppCtx = (ApplicationContext) applicationContext.getBean(defaultParentContextKey);
                        appctx.setParent(parentAppCtx);
                    } else {
                        log.warn("{} bean was not found in context: {}", defaultParentContextKey, 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");
                            parentAppCtx = contextLoader.getContext(defaultParentContextKey);
                            appctx.setParent(parentAppCtx);
                        } else {
                            log.debug("Context loader was not found, trying JMX");
                            MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
                            // get the ContextLoader from jmx
                            ContextLoaderMXBean proxy = null;
                            ObjectName oName = null;
                            try {
                                oName = new ObjectName("org.red5.server:name=contextLoader,type=ContextLoader");
                                if (mbs.isRegistered(oName)) {
                                    proxy = JMX.newMXBeanProxy(mbs, oName, ContextLoaderMXBean.class, true);
                                    log.debug("Context loader was found");
                                    proxy.setParentContext(defaultParentContextKey, appctx.getId());
                                } else {
                                    log.warn("Context loader was not found");
                                }
                            } catch (Exception e) {
                                log.warn("Exception looking up ContextLoader", e);
                            }
                        }
                    }
                    if (log.isDebugEnabled()) {
                        if (appctx.getParent() != null) {
                            log.debug("Parent application context: {}", appctx.getParent().getDisplayName());
                        }
                    }
                    // add the servlet context
                    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();
                }
            };
            thread.setDaemon(true);
            thread.start();
            result = true;
        } catch (Throwable t) {
            log.error("Error setting up context: {} due to: {}", servletContext.getContextPath(), t.getMessage());
            t.printStackTrace();
        } finally {
            //reset the classloader
            Thread.currentThread().setContextClassLoader(originalClassLoader);
        }
        return result;
    }

    /**
     * Set base host.
     * 
     * @param baseHost
     *            Base host
     */
    public void setBaseHost(Host baseHost) {
        log.debug("setBaseHost: {}", baseHost);
        this.host = baseHost;
    }

    /**
     * Get base host.
     * 
     * @return Base host
     */
    public Host getBaseHost() {
        return host;
    }

    /**
     * Return Tomcat engine.
     * 
     * @return Tomcat engine
     */
    public Engine getEngine() {
        return engine;
    }

    /**
     * Set connectors.
     * 
     * @param connectors
     */
    public void setConnectors(List connectors) {
        log.debug("setConnectors: {}", connectors.size());
        this.connectors = connectors;
    }

    /**
     * Set additional contexts.
     * 
     * @param contexts
     *            Map of contexts
     * @throws ServletException
     */
    public void setContexts(Map contexts) throws ServletException {
        log.debug("setContexts: {}", contexts.size());
        for (Map.Entry entry : contexts.entrySet()) {
            host.addChild(embedded.addWebapp(entry.getKey(), webappFolder + entry.getValue()));
        }
    }

    /**
     * Setter for embedded object.
     * 
     * @param embedded
     *            Embedded object
     */
    public void setEmbedded(EmbeddedTomcat embedded) {
        log.info("Setting embedded: {}", embedded.getClass().getName());
        TomcatLoader.embedded = embedded;
    }

    /**
     * Getter for embedded object.
     * 
     * @return Embedded object
     */
    public EmbeddedTomcat getEmbedded() {
        return embedded;
    }

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

    /**
     * Set the host.
     * 
     * @param host
     *            host
     */
    public void setHost(Host host) {
        log.debug("setHost");
        this.host = host;
    }

    /**
     * Set additional hosts.
     * 
     * @param hosts
     *            List of hosts added to engine
     */
    public void setHosts(List hosts) {
        log.debug("setHosts: {}", hosts.size());
        this.hosts = hosts;
    }

    /**
     * Setter for realm.
     * 
     * @param realm
     *            Realm
     */
    public void setRealm(Realm realm) {
        log.info("Setting realm: {}", realm.getClass().getName());
        this.realm = realm;
    }

    /**
     * Getter for realm.
     * 
     * @return Realm
     */
    public Realm getRealm() {
        return realm;
    }

    /**
     * Set additional valves.
     * 
     * @param valves
     *            List of valves
     */
    public void setValves(List valves) {
        log.debug("setValves: {}", valves.size());
        this.valves.addAll(valves);
    }

    /**
     * Returns a semi-unique id for this host based on its host values
     * 
     * @return host id
     */
    protected String getHostId() {
        String hostId = host.getName();
        log.debug("Host id: {}", hostId);
        return hostId;
    }

    protected void registerJMX() {
        // register with jmx
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        try {
            ObjectName oName = new ObjectName("org.red5.server:type=TomcatLoader");
            // check for existing registration before registering
            if (!mbs.isRegistered(oName)) {
                mbs.registerMBean(this, 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 {
            ObjectName oName = new ObjectName("org.red5.server:type=TomcatLoader");
            mbs.unregisterMBean(oName);
        } catch (Exception e) {
            log.warn("Exception unregistering", e);
        }
    }

    /**
     * Shut server down.
     */
    @Override
    public void destroy() throws Exception {
        log.info("Shutting down Tomcat context");
        // run through the applications and ensure that spring is told to commence shutdown / disposal
        AbstractApplicationContext absCtx = (AbstractApplicationContext) LoaderBase.getApplicationContext();
        if (absCtx != null) {
            log.debug("Using loader base application context for shutdown");
            // get all the app (web) contexts and shut them down first
            Map contexts = LoaderBase.getRed5ApplicationContexts();
            if (contexts.isEmpty()) {
                log.info("No contexts were found to shutdown");
            }
            for (Map.Entry entry : contexts.entrySet()) {
                // stop the context
                log.debug("Calling stop on context: {}", entry.getKey());
                entry.getValue().stop();
            }
            if (absCtx.isActive()) {
                log.debug("Closing application context");
                absCtx.close();
            }
        } else {
            log.error("Error getting Spring bean factory for shutdown");
        }
        try {
            // stop tomcat
            embedded.stop();
        } catch (Exception e) {
            log.warn("Tomcat could not be stopped", e);
            throw new RuntimeException("Tomcat could not be stopped");
        }
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "TomcatLoader [serviceEngineName=" + serviceEngineName + "]";
    }

    /**
     * Get cluster
     * @return cluster object
     */
    public Cluster getCluster() {
		return cluster;
	}

    /**
     * Set cluster
     * @param cluster object
     */
	public void setCluster(Cluster cluster) {
		this.cluster = cluster;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy