Java tutorial
/** * 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 psiprobe; import org.apache.catalina.Container; import org.apache.catalina.Context; import org.apache.catalina.Engine; import org.apache.catalina.Host; import org.apache.catalina.Service; import org.apache.catalina.Valve; import org.apache.catalina.Wrapper; import org.apache.catalina.connector.Connector; import org.apache.catalina.core.StandardContext; import org.apache.commons.modeler.Registry; import org.apache.jasper.EmbeddedServletOptions; import org.apache.jasper.JspCompilationContext; import org.apache.jasper.Options; import org.apache.jasper.compiler.JspRuntimeContext; import org.apache.naming.ContextBindings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.ClassUtils; import psiprobe.model.jsp.Item; import psiprobe.model.jsp.Summary; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.naming.NamingException; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; /** * Abstraction layer to implement some functionality, which is common between different container * adapters. */ public abstract class AbstractTomcatContainer implements TomcatContainer { /** The logger. */ protected final Logger logger = LoggerFactory.getLogger(getClass()); /** The host. */ protected Host host; /** Connectors. */ protected Connector[] connectors; /** The deployer o name. */ protected ObjectName deployerOName; /** The mbean server. */ protected MBeanServer mbeanServer; @Override public void setWrapper(Wrapper wrapper) { Valve valve = createValve(); if (wrapper != null) { host = (Host) wrapper.getParent().getParent(); Engine engine = (Engine) host.getParent(); Service service = engine.getService(); connectors = service.findConnectors(); try { deployerOName = new ObjectName( host.getParent().getName() + ":type=Deployer,host=" + host.getName()); } catch (MalformedObjectNameException e) { logger.trace("", e); } host.getPipeline().addValve(valve); mbeanServer = Registry.getRegistry(null, null).getMBeanServer(); } else if (host != null) { host.getPipeline().removeValve(valve); } } @Override public File getAppBase() { File base = new File(host.getAppBase()); if (!base.isAbsolute()) { base = new File(System.getProperty("catalina.base"), host.getAppBase()); } return base; } @Override public String getConfigBase() { File configBase = new File(System.getProperty("catalina.base"), "conf"); Container baseHost = null; Container thisContainer = host; while (thisContainer != null) { if (thisContainer instanceof Host) { baseHost = thisContainer; } thisContainer = thisContainer.getParent(); } if (baseHost != null) { configBase = new File(configBase, baseHost.getName()); } return configBase.getAbsolutePath(); } @Override public String getHostName() { return host.getName(); } @Override public String getName() { return host.getParent().getName(); } @Override public List<Context> findContexts() { List<Context> results = new ArrayList<>(); for (Container child : host.findChildren()) { if (child instanceof Context) { results.add((Context) child); } } return results; } @Override public List<Connector> findConnectors() { return Collections.unmodifiableList(Arrays.asList(connectors)); } @Override public boolean installContext(String contextName) throws Exception { contextName = formatContextName(contextName); String contextFilename = formatContextFilename(contextName); File contextFile = new File(getConfigBase(), contextFilename + ".xml"); installContextInternal(contextName, contextFile); return findContext(contextName) != null; } @Override public void stop(String name) throws Exception { Context ctx = findContext(name); if (ctx != null) { ctx.stop(); } } @Override public void start(String name) throws Exception { Context ctx = findContext(name); if (ctx != null) { ctx.start(); } } @Override public void remove(String name) throws Exception { name = formatContextName(name); Context ctx = findContext(name); if (ctx != null) { try { stop(name); } catch (Exception e) { logger.info("Stopping '{}' threw this exception:", name, 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(name); 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(name); } } /** * Removes the internal. * * @param name the name * @throws Exception the exception */ private void removeInternal(String name) throws Exception { checkChanges(name); } @Override public void installWar(String name, URL url) throws Exception { checkChanges(name); } /** * Install context internal. * * @param name the name * @param config the config * @throws Exception the exception */ private void installContextInternal(String name, File config) throws Exception { checkChanges(name); } @Override 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; } @Override 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; } @Override 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; } } @Override 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 '{}' is not an instance of '{}', expected StandardContext", context.getName(), context.getClass().getName()); } } @Override 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, opt, sctx, jrctx, null); servletName = jcctx.getServletJavaFileName(); } else { logger.error("Context '{}' does not have 'JSP' servlet", context.getName()); } return servletName; } @Override public void recompileJsps(Context context, Summary summary, List<String> 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); /* * 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 */ try (URLClassLoader classLoader = new URLClassLoader(new URL[0], context.getLoader().getClassLoader())) { for (String name : names) { long time = System.currentTimeMillis(); JspCompilationContext jcctx = createJspCompilationContext(name, opt, sctx, jrctx, classLoader); ClassLoader prevCl = ClassUtils.overrideThreadContextClassLoader(classLoader); try { Item item = summary.getItems().get(name); if (item != null) { try { org.apache.jasper.compiler.Compiler compiler = jcctx.createCompiler(); compiler.compile(); item.setState(Item.STATE_READY); item.setException(null); logger.info("Compiled '{}': OK", name); } catch (Exception e) { item.setState(Item.STATE_FAILED); item.setException(e); logger.info("Compiled '{}': FAILED", name, e); } item.setCompileTime(System.currentTimeMillis() - time); } else { logger.error("{} is not on the summary list, ignored", name); } } finally { ClassUtils.overrideThreadContextClassLoader(prevCl); } } } catch (IOException e) { this.logger.error("", e); } finally { jrctx.destroy(); } } } else { logger.error("summary is null for '{}', request ignored", context.getName()); } } else { logger.error("Context '{}' does not have 'JSP' servlet", context.getName()); } } @Override public void listContextJsps(Context context, Summary summary, boolean compile) { 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<String, Item>()); } /* * mark all items as missing */ for (Item item : summary.getItems().values()) { 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 */ URLClassLoader urlcl = new URLClassLoader(new URL[0], context.getLoader().getClassLoader()); compileItem("/", opt, context, jrctx, summary, urlcl, 0, compile); } finally { jrctx.destroy(); } } // // delete "missing" items by keeping "not missing" ones // Map<String, Item> hashMap = new HashMap<>(); for (String key : summary.getItems().keySet()) { Item item = summary.getItems().get(key); if (!item.isMissing()) { hashMap.put(key, item); } } summary.setItems(hashMap); } else { logger.error("Context '{}' does not have 'JSP' servlet", context.getName()); } } @Override public boolean getAvailable(Context context) { return context.getState().isAvailable(); } @Override public File getConfigFile(Context context) { URL configUrl = context.getConfigFile(); if (configUrl != null) { try { URI configUri = configUrl.toURI(); if ("file".equals(configUri.getScheme())) { return new File(configUri.getPath()); } } catch (URISyntaxException ex) { logger.error("Could not convert URL to URI: '{}'", configUrl, ex); } } return null; } @Override public void bindToContext(Context context) throws NamingException { changeContextBinding(context, true); } @Override public void unbindFromContext(Context context) throws NamingException { changeContextBinding(context, false); } /** * Change context binding. * * @param context the context * @param bind the bind * @throws NamingException the naming exception */ private void changeContextBinding(Context context, boolean bind) throws NamingException { Object token = getNamingToken(context); ClassLoader loader = Thread.currentThread().getContextClassLoader(); if (bind) { ContextBindings.bindClassLoader(context, token, loader); } else { ContextBindings.unbindClassLoader(context, token, loader); } } /** * Lists and optionally compiles a directory recursively. * * @param jspName name of JSP file or directory to be listed and compiled. * @param opt the JSP compiler options * @param ctx the context * @param jrctx the runtime context used to create the compilation context * @param summary the summary in which the output is stored * @param classLoader the classloader used by the compiler * @param level the depth in the tree at which the item was encountered * @param compile whether or not to compile the item or just to check whether it's out of date */ protected void compileItem(String jspName, Options opt, Context ctx, JspRuntimeContext jrctx, Summary summary, URLClassLoader classLoader, int level, boolean compile) { ServletContext sctx = ctx.getServletContext(); Set<String> paths = sctx.getResourcePaths(jspName); if (paths != null) { for (String name : paths) { boolean isJsp = false; try { isJsp = name.endsWith(".jsp") || name.endsWith(".jspx") || opt.getJspConfig().isJspPage(name); } catch (Exception e) { // XXX Tomcat 7.0.x throws JasperException otherwise this could be removed. logger.info("isJspPage() thrown an error for '{}'", name, e); } if (isJsp) { JspCompilationContext jcctx = createJspCompilationContext(name, opt, sctx, jrctx, classLoader); ClassLoader prevCl = ClassUtils.overrideThreadContextClassLoader(classLoader); try { 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]); item.setLastModified(objects[1]); long time = System.currentTimeMillis(); try { org.apache.jasper.compiler.Compiler compiler = jcctx.createCompiler(); if (compile) { compiler.compile(); item.setState(Item.STATE_READY); item.setException(null); } else { if (!compiler.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 '{}': OK", name); } catch (Exception e) { item.setState(Item.STATE_FAILED); item.setException(e); logger.info("Compiled '{}': FAILED", name, 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 '{}'. Empty dir? Or Tomcat bug?", jspName); } } /** * Find context internal. * * @param name the context name * @return the context */ protected Context findContextInternal(String name) { return (Context) host.findChild(name); } /** * Check changes. * * @param name the name * @throws Exception the exception */ protected void checkChanges(String name) throws Exception { Boolean result = (Boolean) mbeanServer.invoke(deployerOName, "isServiced", new String[] { name }, new String[] { "java.lang.String" }); if (!result) { mbeanServer.invoke(deployerOName, "addServiced", new String[] { name }, new String[] { "java.lang.String" }); try { mbeanServer.invoke(deployerOName, "check", new String[] { name }, new String[] { "java.lang.String" }); } finally { mbeanServer.invoke(deployerOName, "removeServiced", new String[] { name }, new String[] { "java.lang.String" }); } } } /** * Returns the security token required to bind to a naming context. * * @param context the catalina context * * @return the security token for use with <code>ContextBindings</code> */ protected abstract Object getNamingToken(Context context); /** * Creates the jsp compilation context. * * @param name the name * @param opt the opt * @param sctx the sctx * @param jrctx the jrctx * @param classLoader the class loader * @return the jsp compilation context */ protected abstract JspCompilationContext createJspCompilationContext(String name, Options opt, ServletContext sctx, JspRuntimeContext jrctx, ClassLoader classLoader); protected abstract Valve createValve(); }