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 }