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    }