001 package com.khubla.pragmatach.plugin.jsp; 002 003 import java.io.File; 004 import java.net.URL; 005 import java.net.URLClassLoader; 006 import java.util.ArrayList; 007 import java.util.Collection; 008 import java.util.List; 009 010 import javax.servlet.ServletConfig; 011 import javax.servlet.ServletContext; 012 013 import org.apache.commons.io.FileUtils; 014 import org.apache.commons.lang.StringUtils; 015 import org.apache.jasper.EmbeddedServletOptions; 016 import org.apache.jasper.JspCompilationContext; 017 import org.apache.jasper.Options; 018 import org.apache.jasper.compiler.Compiler; 019 import org.apache.jasper.compiler.JspRuntimeContext; 020 import org.apache.jasper.runtime.HttpJspBase; 021 import org.slf4j.Logger; 022 import org.slf4j.LoggerFactory; 023 024 import com.khubla.pragmatach.framework.api.PragmatachException; 025 026 /** 027 * Simple JSP compiler 028 * <p> 029 * http://javasourcecode.org/html/open-source/tomcat/tomcat-7.0.29/org/apache/jasper/JspCompilationContext.java.html 030 * </p> 031 * <p> 032 * http://javasourcecode.org/html/open-source/tomcat/tomcat-6.0.32/org/apache/jasper/JspC.java.html 033 * </p> 034 * <p> 035 * http://javasourcecode.org/html/open-source/tomcat/tomcat-7.0.29/org/apache/jasper/JspC.java.html 036 * </p> 037 * 038 * @author tome 039 */ 040 public class JSPCompiler { 041 /** 042 * class cache 043 */ 044 private static JSPClassCache jspClassCache = new JSPClassCache(); 045 /** 046 * namespace 047 */ 048 private static final String JSP_NAMESPACE = "com.khubla.pragmatach.jsp"; 049 /** 050 * servlet context 051 */ 052 private final ServletContext servletContext; 053 /** 054 * servlet config 055 */ 056 private final ServletConfig servletConfig; 057 /** 058 * logger 059 */ 060 private final Logger logger = LoggerFactory.getLogger(this.getClass()); 061 /** 062 * classloader 063 */ 064 private URLClassLoader compilerClassLoader; 065 /** 066 * options 067 */ 068 private final Options options; 069 /** 070 * file 071 */ 072 private final String jspFile; 073 /** 074 * the classname 075 */ 076 final String className; 077 /** 078 * the class package 079 */ 080 final String packageName; 081 /** 082 * the fully qualified classname 083 */ 084 final String fullyQualifiedClassName; 085 /** 086 * the path to the classfile 087 */ 088 final String classFilePath; 089 090 /** 091 * ctor 092 */ 093 public JSPCompiler(String jspFile, ServletConfig servletConfig, ServletContext servletContext) { 094 this.servletContext = servletContext; 095 this.servletConfig = servletConfig; 096 options = new EmbeddedServletOptions(servletConfig, servletContext); 097 this.jspFile = jspFile; 098 compilerClassLoader = createCompilerClassLoader(jspFile); 099 className = makeClassName(); 100 packageName = getPackage(); 101 fullyQualifiedClassName = getFullyQualifiedClassName(); 102 classFilePath = getClassFilePath(); 103 } 104 105 /** 106 * compile a jspFile and return the class type 107 */ 108 private void compile() throws PragmatachException { 109 ClassLoader originalClassLoader = null; 110 try { 111 /* 112 * get description of the class we want to create 113 */ 114 final String className = makeClassName(); 115 final String packageName = getPackage(); 116 final String fullyQualifiedClassName = getFullyQualifiedClassName(); 117 /* 118 * log 119 */ 120 logger.info("Compiling '" + jspFile + "' to '" + fullyQualifiedClassName + "' in directory '" + getPackageDir(packageName) + "'"); 121 /* 122 * options 123 */ 124 final Options options = new EmbeddedServletOptions(servletConfig, servletContext); 125 /* 126 * runtime context 127 */ 128 final JspRuntimeContext jspRuntimeContext = new JspRuntimeContext(servletContext, options); 129 /* 130 * set up class compilation context 131 */ 132 final String jspUri = jspFile.replace('\\', '/'); 133 final JspCompilationContext jspCompilationContext = new JspCompilationContext(jspUri, false, options, servletContext, null, jspRuntimeContext); 134 jspCompilationContext.setServletClassName(className); 135 jspCompilationContext.setServletPackageName(packageName); 136 /* 137 * save the class loader and set new class loader 138 */ 139 originalClassLoader = Thread.currentThread().getContextClassLoader(); 140 Thread.currentThread().setContextClassLoader(compilerClassLoader); 141 jspCompilationContext.setClassLoader(compilerClassLoader); 142 /* 143 * compile 144 */ 145 final Compiler compiler = jspCompilationContext.createCompiler(); 146 if (null != compiler) { 147 compiler.compile(); 148 } else { 149 throw new Exception("Unable to create compiler"); 150 } 151 /* 152 * refresh the classloader 153 */ 154 compilerClassLoader = createCompilerClassLoader(jspUri); 155 } catch (final Exception e) { 156 throw new PragmatachException("Exception in compile", e); 157 } finally { 158 if (originalClassLoader != null) { 159 Thread.currentThread().setContextClassLoader(originalClassLoader); 160 } 161 } 162 } 163 164 /** 165 * set up the class loader for the JSP compiler 166 */ 167 private URLClassLoader createCompilerClassLoader(String jspFile) { 168 try { 169 /* 170 * the urls 171 */ 172 final List<URL> urls = new ArrayList<URL>(); 173 /* 174 * find the files in /WEB-INF/classes/ 175 */ 176 final String rootURI = servletContext.getRealPath(File.separator); 177 final File classesDir = new File(rootURI + "/WEB-INF/classes/"); 178 if (classesDir.exists()) { 179 final Collection<File> jars = FileUtils.listFiles(classesDir, new String[] { "jar" }, true); 180 for (final File jar : jars) { 181 urls.add(new URL("file://" + jar.getAbsolutePath())); 182 } 183 } 184 /* 185 * find the files in /WEB-INF/lib/ 186 */ 187 final File libsDir = new File(rootURI + "/WEB-INF/lib/"); 188 if (libsDir.exists()) { 189 final Collection<File> jars = FileUtils.listFiles(libsDir, new String[] { "jar" }, true); 190 for (final File jar : jars) { 191 urls.add(new URL("file://" + jar.getAbsolutePath())); 192 } 193 } 194 /* 195 * show all urls 196 */ 197 for (final URL url : urls) { 198 logger.debug(url.getPath()); 199 } 200 /* 201 * done 202 */ 203 final URL[] u = new URL[urls.size()]; 204 urls.toArray(u); 205 return new URLClassLoader(u); 206 } catch (final Exception e) { 207 logger.error("Exception in createCompilerClassLoader", e); 208 return null; 209 } 210 } 211 212 private URLClassLoader createJSPClassLoader() { 213 try { 214 return new URLClassLoader(new URL[] { new URL("file://" + options.getScratchDir().getAbsolutePath() + "/") }, Thread.currentThread().getContextClassLoader()); 215 } catch (final Exception e) { 216 logger.error("Exception in createJSPClassLoader", e); 217 return null; 218 } 219 } 220 221 private final String getClassFilePath() { 222 return getPackageDir(getPackage()) + "/" + makeClassName() + ".class"; 223 } 224 225 /** 226 * get a Class<?> for the jspFile 227 */ 228 private Class<?> getClazz() throws PragmatachException { 229 try { 230 /* 231 * get from cache 232 */ 233 Class<?> ret = jspClassCache.find(fullyQualifiedClassName); 234 if (null == ret) { 235 /* 236 * compile 237 */ 238 compile(); 239 /* 240 * get it 241 */ 242 final URLClassLoader jspClassLoader = createJSPClassLoader(); 243 ret = jspClassLoader.loadClass(fullyQualifiedClassName); 244 /* 245 * cache it 246 */ 247 jspClassCache.add(ret, fullyQualifiedClassName); 248 } 249 /* 250 * done 251 */ 252 return ret; 253 } catch (final Exception e) { 254 throw new PragmatachException("Exception in getClazz", e); 255 } 256 } 257 258 /** 259 * get jspFile fully qualified name 260 */ 261 private String getFullyQualifiedClassName() { 262 return getPackage() + "." + makeClassName(); 263 } 264 265 /** 266 * get jspFile package 267 */ 268 private String getPackage() { 269 final String classPart = jspFile.replaceAll(File.separator, "."); 270 final int i = classPart.lastIndexOf(File.separator); 271 if (-1 != i) { 272 return JSP_NAMESPACE + "." + classPart.substring(0, i); 273 } else { 274 return JSP_NAMESPACE; 275 } 276 } 277 278 /** 279 * get the absolute dir that files in a specific package will end up in 280 */ 281 private String getPackageDir(String packageName) { 282 return options.getScratchDir().getAbsolutePath() + File.separator + StringUtils.replace(packageName, ".", File.separator); 283 } 284 285 /** 286 * given a JSP file relative path, return a Servlet 287 */ 288 public HttpJspBase getServlet() throws PragmatachException { 289 try { 290 final Class<?> clazz = getClazz(); 291 final Object o = clazz.newInstance(); 292 return (HttpJspBase) o; 293 } catch (final Exception e) { 294 throw new PragmatachException("Exception in getServlet", e); 295 } 296 } 297 298 /** 299 * create classname from jspFile 300 */ 301 private String makeClassName() { 302 /* 303 * get the part after the last dot 304 */ 305 String ret = jspFile.substring(0, jspFile.indexOf(".")); 306 final int i = ret.lastIndexOf(File.separator); 307 if (-1 != i) { 308 ret = ret.substring(i + 1); 309 } 310 /* 311 * capitalize first letter 312 */ 313 ret = ret.substring(0, 1).toUpperCase() + ret.substring(1); 314 /* 315 * done 316 */ 317 return ret; 318 } 319 }