com.googlecode.psiprobe.AbstractTomcatContainer.java Source code

Java tutorial

Introduction

Here is the source code for com.googlecode.psiprobe.AbstractTomcatContainer.java

Source

/*
 * Licensed under the GPL License.  You may not use this file except in
 * compliance with the License.  You may obtain a copy of the License at
 *
 *     http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 *
 * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */
package com.googlecode.psiprobe;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.naming.NamingException;

import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.core.StandardContext;
import org.apache.naming.ContextBindings;
import org.apache.commons.lang.reflect.MethodUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jasper.EmbeddedServletOptions;
import org.apache.jasper.JasperException;
import org.apache.jasper.JspCompilationContext;
import org.apache.jasper.Options;
import org.apache.jasper.compiler.JspRuntimeContext;
import org.springframework.util.ClassUtils;

import com.googlecode.psiprobe.model.jsp.Item;
import com.googlecode.psiprobe.model.jsp.Summary;

/**
 * Abstraction layer to implement some functionality, which is common between different container adaptors.
 * 
 * @author Vlad Ilyushchenko
 * @author Mark Lewis
 */
public abstract class AbstractTomcatContainer implements TomcatContainer {

    protected Log logger = LogFactory.getLog(getClass());

    public boolean installContext(String contextName) throws Exception {
        contextName = formatContextName(contextName);
        String contextFilename = formatContextFilename(contextName);
        File f = new File(getConfigBase(), contextFilename + ".xml");
        installContextInternal(contextName, f);
        return findContext(contextName) != null;
    }

    public void remove(String contextName) throws Exception {
        contextName = formatContextName(contextName);
        Context ctx = findContext(contextName);

        if (ctx != null) {

            try {
                stop(contextName);
            } catch (Throwable e) {
                logger.info("Stopping " + contextName + " threw this exception:", e);
                //
                // make sure we always re-throw ThreadDeath
                //
                if (e instanceof ThreadDeath) {
                    throw (ThreadDeath) e;
                }
            }

            File appDir;
            File docBase = new File(ctx.getDocBase());

            if (!docBase.isAbsolute()) {
                appDir = new File(getAppBase(), ctx.getDocBase());
            } else {
                appDir = docBase;
            }

            logger.debug("Deleting " + appDir.getAbsolutePath());
            Utils.delete(appDir);

            String warFilename = formatContextFilename(contextName);
            File warFile = new File(getAppBase(), warFilename + ".war");
            logger.debug("Deleting " + warFile.getAbsolutePath());
            Utils.delete(warFile);

            File configFile = getConfigFile(ctx);
            if (configFile != null) {
                logger.debug("Deleting " + configFile.getAbsolutePath());
                Utils.delete(configFile);
            }

            removeInternal(contextName);
        }
    }

    /**
      * Binds a naming context to the current thread's classloader.
      * 
      * @param context the catalina context
      */
    public void bindToContext(Context context) throws NamingException {
        Object token = null;
        ContextBindings.bindClassLoader(context, token, Thread.currentThread().getContextClassLoader());
    }

    /**
      * Unbinds a naming context from the current thread's classloader.
      * 
      * @param context the catalina context
      */
    public void unbindFromContext(Context context) throws NamingException {
        Object token = null;
        ContextBindings.unbindClassLoader(context, token, Thread.currentThread().getContextClassLoader());
    }

    public Context findContext(String name) {
        String safeName = formatContextName(name);
        if (safeName == null) {
            return null;
        }
        Context result = findContextInternal(safeName);
        if (result == null && "".equals(safeName)) {
            result = findContextInternal("/");
        }
        return result;
    }

    public String formatContextName(String name) {
        if (name == null) {
            return null;
        }
        String result = name.trim();
        if (!result.startsWith("/")) {
            result = "/" + result;
        }
        if ("/".equals(result) || "/ROOT".equals(result)) {
            result = "";
        }
        return result;
    }

    public String formatContextFilename(String contextName) {
        if (contextName == null) {
            return null;
        } else if ("".equals(contextName)) {
            return "ROOT";
        } else if (contextName.startsWith("/")) {
            return contextName.substring(1);
        } else {
            return contextName;
        }
    }

    public void discardWorkDir(Context context) {
        if (context instanceof StandardContext) {
            StandardContext standardContext = (StandardContext) context;
            logger.info("Discarding " + standardContext.getWorkPath());
            Utils.delete(new File(standardContext.getWorkPath(), "org"));
        } else {
            logger.error("context " + context.getName() + " is not an instance of " + context.getClass().getName()
                    + ", expected StandardContext");
        }
    }

    public String getServletFileNameForJsp(Context context, String jspName) {
        String servletName = null;

        ServletConfig servletConfig = (ServletConfig) context.findChild("jsp");
        if (servletConfig != null) {
            ServletContext sctx = context.getServletContext();
            Options opt = new EmbeddedServletOptions(servletConfig, sctx);
            JspRuntimeContext jrctx = new JspRuntimeContext(sctx, opt);
            JspCompilationContext jcctx = createJspCompilationContext(jspName, false, opt, sctx, jrctx, null);
            servletName = jcctx.getServletJavaFileName();
        } else {
            logger.error("Context " + context.getName() + " does not have \"jsp\" servlet");
        }
        return servletName;
    }

    /**
     * Compiles a list of JSPs. Names of JSP files are expected to be relative to the webapp root. The method
     * updates summary with compilation details.
     *
     * @param context
     * @param summary
     * @param names
     */
    public void recompileJsps(Context context, Summary summary, List names) {
        ServletConfig servletConfig = (ServletConfig) context.findChild("jsp");
        if (servletConfig != null) {
            if (summary != null) {
                synchronized (servletConfig) {
                    ServletContext sctx = context.getServletContext();
                    Options opt = new EmbeddedServletOptions(servletConfig, sctx);

                    JspRuntimeContext jrctx = new JspRuntimeContext(sctx, opt);
                    try {
                        //
                        // we need to pass context classloader here, so the jsps can reference /WEB-INF/classes and
                        // /WEB-INF/lib. JspCompilationContext would only take URLClassLoader, so we fake it
                        //
                        URLClassLoader classLoader = new URLClassLoader(new URL[] {},
                                context.getLoader().getClassLoader());
                        for (Iterator it = names.iterator(); it.hasNext();) {
                            String name = (String) it.next();
                            long time = System.currentTimeMillis();
                            JspCompilationContext jcctx = createJspCompilationContext(name, false, opt, sctx, jrctx,
                                    classLoader);
                            ClassLoader prevCl = ClassUtils.overrideThreadContextClassLoader(classLoader);
                            try {
                                Item item = (Item) summary.getItems().get(name);
                                if (item != null) {
                                    try {
                                        org.apache.jasper.compiler.Compiler c = jcctx.createCompiler();
                                        c.compile();
                                        item.setState(Item.STATE_READY);
                                        item.setException(null);
                                        logger.info("Compiled " + name + ": OK");
                                    } catch (Exception e) {
                                        item.setState(Item.STATE_FAILED);
                                        item.setException(e);
                                        logger.info("Compiled " + name + ": FAILED", e);
                                    }
                                    item.setCompileTime(System.currentTimeMillis() - time);
                                } else {
                                    logger.error(name + " is not on the summary list, ignored");
                                }
                            } finally {
                                ClassUtils.overrideThreadContextClassLoader(prevCl);
                            }
                        }
                    } finally {
                        jrctx.destroy();
                    }
                }
            } else {
                logger.error("summary is null for " + context.getName() + ", request ignored");
            }
        } else {
            logger.error("Context " + context.getName() + " does not have \"jsp\" servlet");
        }
    }

    /**
     * Lists and optionally compiles all JSPs for the given context. Compilation details are added to the summary.
     *
     * @param context
     * @param summary
     * @param compile
     * @throws Exception
     */
    public void listContextJsps(Context context, Summary summary, boolean compile) throws Exception {
        ServletConfig servletConfig = (ServletConfig) context.findChild("jsp");
        if (servletConfig != null) {
            synchronized (servletConfig) {
                ServletContext sctx = context.getServletContext();
                Options opt = new EmbeddedServletOptions(servletConfig, sctx);

                JspRuntimeContext jrctx = new JspRuntimeContext(sctx, opt);
                try {
                    if (summary.getItems() == null) {
                        summary.setItems(new HashMap());
                    }

                    //
                    // mark all items as missing
                    //
                    for (Iterator it = summary.getItems().keySet().iterator(); it.hasNext();) {
                        Item item = (Item) summary.getItems().get(it.next());
                        item.setMissing(true);
                    }

                    //
                    // we need to pass context classloader here, so the jsps can reference /WEB-INF/classes and
                    // /WEB-INF/lib. JspCompilationContext would only take URLClassLoader, so we fake it
                    //
                    compileItem("/", opt, context, jrctx, summary,
                            new URLClassLoader(new URL[] {}, context.getLoader().getClassLoader()), 0, compile);
                } finally {
                    jrctx.destroy();
                }
            }

            //
            // delete "missing" items by keeping "not missing" ones
            //
            Map hashMap = new HashMap();
            for (Iterator it = summary.getItems().keySet().iterator(); it.hasNext();) {
                Object key = it.next();
                Item item = (Item) summary.getItems().get(key);
                if (!item.isMissing()) {
                    hashMap.put(key, item);
                }
            }

            summary.setItems(hashMap);
        } else {
            logger.error("Context " + context.getName() + " does not have \"jsp\" servlet");
        }
    }

    public File getConfigFile(Context ctx) {
        String configFilePath = ctx.getConfigFile();
        if (configFilePath != null) {
            return new File(configFilePath);
        } else {
            return null;
        }
    }

    protected String getConfigBase(Container container) {
        File configBase = new File(System.getProperty("catalina.base"), "conf");
        Container baseHost = null;
        Container baseEngine = null;
        while (container != null) {
            if (container instanceof Host) {
                baseHost = container;
            }
            if (container instanceof Engine) {
                baseEngine = container;
            }
            container = container.getParent();
        }
        if (baseEngine != null) {
            configBase = new File(configBase, baseEngine.getName());
        }
        if (baseHost != null) {
            configBase = new File(configBase, baseHost.getName());
        }
        return configBase.getAbsolutePath();
    }

    /**
     * Lists and optionally compiles a directory recursively.
     *
     * @param jspName     name of JSP file or directory to be listed and compiled.
     * @param opt
     * @param ctx
     * @param jrctx
     * @param summary
     * @param classLoader
     * @param level
     * @param compile
     */
    protected void compileItem(String jspName, Options opt, Context ctx, JspRuntimeContext jrctx, Summary summary,
            URLClassLoader classLoader, int level, boolean compile) {
        ServletContext sctx = ctx.getServletContext();
        Set paths = sctx.getResourcePaths(jspName);

        if (paths != null) {
            for (Iterator it = paths.iterator(); it.hasNext();) {
                String name = (String) it.next();
                boolean isJsp = false;

                try {
                    isJsp = name.endsWith(".jsp") || name.endsWith(".jspx") || opt.getJspConfig().isJspPage(name);
                } catch (JasperException e) {
                    logger.info("isJspPage() thrown an error for " + name, e);
                }

                if (isJsp) {
                    JspCompilationContext jcctx = createJspCompilationContext(name, false, opt, sctx, jrctx,
                            classLoader);
                    ClassLoader prevCl = ClassUtils.overrideThreadContextClassLoader(classLoader);
                    try {
                        Item item = (Item) summary.getItems().get(name);

                        if (item == null) {
                            item = new Item();
                            item.setName(name);
                        }

                        item.setLevel(level);
                        item.setCompileTime(-1);

                        Long objects[] = this.getResourceAttributes(name, ctx);
                        item.setSize(objects[0].longValue());
                        item.setLastModified(objects[1].longValue());
                        //                        } catch (NamingException e) {
                        //                            logger.error("Cannot lookup attributes for " + name);
                        //                        }

                        long time = System.currentTimeMillis();
                        try {
                            org.apache.jasper.compiler.Compiler c = jcctx.createCompiler();
                            if (compile) {
                                c.compile();
                                item.setState(Item.STATE_READY);
                                item.setException(null);
                            } else {
                                if (!c.isOutDated()) {
                                    item.setState(Item.STATE_READY);
                                    item.setException(null);
                                } else if (item.getState() != Item.STATE_FAILED) {
                                    item.setState(Item.STATE_OOD);
                                    item.setException(null);
                                }
                            }
                            logger.info("Compiled " + name + ": OK");
                        } catch (Exception e) {
                            item.setState(Item.STATE_FAILED);
                            item.setException(e);
                            logger.info("Compiled " + name + ": FAILED", e);
                        }
                        if (compile) {
                            item.setCompileTime(System.currentTimeMillis() - time);
                        }
                        item.setMissing(false);
                        summary.getItems().put(name, item);
                    } finally {
                        ClassUtils.overrideThreadContextClassLoader(prevCl);
                    }
                } else {
                    compileItem(name, opt, ctx, jrctx, summary, classLoader, level + 1, compile);
                }
            }
        } else {
            logger.debug("getResourcePaths() is null for " + jspName + ". Empty dir? Or Tomcat bug?");
        }
    }

    protected JspCompilationContext createJspCompilationContext(String name, boolean isErrPage, Options opt,
            ServletContext sctx, JspRuntimeContext jrctx, ClassLoader cl) {
        JspCompilationContext jcctx = new JspCompilationContext(name, false, opt, sctx, null, jrctx);
        if (cl != null && cl instanceof URLClassLoader) {
            try {
                jcctx.setClassLoader((URLClassLoader) cl);
            } catch (NoSuchMethodError err) {
                //JBoss Web 2.1 has a different method signature for setClassLoader().
                try {
                    MethodUtils.invokeMethod(jcctx, "setClassLoader", cl);
                } catch (NoSuchMethodException ex) {
                    throw new RuntimeException(ex);
                } catch (IllegalAccessException ex) {
                    throw new RuntimeException(ex);
                } catch (InvocationTargetException ex) {
                    throw new RuntimeException(ex);
                }
            }
        }
        return jcctx;
    }

    protected abstract void removeInternal(String name) throws Exception;

    protected abstract void installContextInternal(String contextName, File f) throws Exception;

    protected abstract Context findContextInternal(String contextName);

}