View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.struts2.jasper;
19  
20  import com.opensymphony.xwork2.util.finder.ClassLoaderInterface;
21  import com.opensymphony.xwork2.util.logging.Logger;
22  import com.opensymphony.xwork2.util.logging.LoggerFactory;
23  import org.apache.struts2.jasper.compiler.Compiler;
24  import org.apache.struts2.jasper.compiler.*;
25  import org.apache.struts2.jasper.servlet.JasperLoader;
26  import org.apache.struts2.jasper.servlet.JspServletWrapper;
27  import org.apache.commons.lang.xwork.StringUtils;
28  
29  import javax.servlet.ServletContext;
30  import javax.servlet.jsp.tagext.TagInfo;
31  import java.io.FileNotFoundException;
32  import java.io.IOException;
33  import java.net.MalformedURLException;
34  import java.net.URL;
35  import java.net.URLClassLoader;
36  import java.util.Hashtable;
37  import java.util.Set;
38  
39  /***
40   * A place holder for various things that are used through out the JSP
41   * engine. This is a per-request/per-context data structure. Some of
42   * the instance variables are set at different points.
43   * <p/>
44   * Most of the path-related stuff is here - mangling names, versions, dirs,
45   * loading resources and dealing with uris.
46   *
47   * @author Anil K. Vijendran
48   * @author Harish Prabandham
49   * @author Pierre Delisle
50   * @author Costin Manolache
51   * @author Kin-man Chung
52   */
53  public class JspCompilationContext {
54  
55      private Logger log = LoggerFactory.getLogger(JspCompilationContext.class);
56  
57      private Hashtable tagFileJarUrls;
58      private boolean isPackagedTagFile;
59  
60      private String className;
61      private String jspUri;
62      private boolean isErrPage;
63      private String basePackageName;
64      private String derivedPackageName;
65      private String servletJavaFileName;
66      private String javaPath;
67      private String classFileName;
68      private String contentType;
69      private ServletWriter writer;
70      private Options options;
71      private JspServletWrapper jsw;
72      private Compiler jspCompiler;
73      private String classPath;
74  
75  
76      private ServletContext context;
77      private URLClassLoader loader;
78  
79      private JspRuntimeContext rctxt;
80  
81      private int removed = 0;
82  
83      private URLClassLoader jspLoader;
84      private URL baseUrl;
85      private Class servletClass;
86  
87      private boolean isTagFile;
88      private boolean protoTypeMode;
89      private TagInfo tagInfo;
90      private URL tagFileJarUrl;
91  
92      private String sourceCode;
93      private ClassLoaderInterface classLoaderInterface;
94  
95      public String getSourceCode() {
96          return sourceCode;
97      }
98  
99      public void setSourceCode(String sourceCode) {
100         this.sourceCode = sourceCode;
101     }
102 
103     public JspCompilationContext(String jspUri,
104                                  boolean isErrPage,
105                                  Options options,
106                                  ServletContext context,
107                                  JspServletWrapper jsw,
108                                  JspRuntimeContext rctxt,
109                                  ClassLoaderInterface classLoaderInterface) {
110 
111         this.jspUri = canonicalURI(jspUri);
112         this.isErrPage = isErrPage;
113         this.options = options;
114         this.jsw = jsw;
115         this.context = context;
116 
117         this.rctxt = rctxt;
118         this.tagFileJarUrls = new Hashtable();
119         this.basePackageName = Constants.JSP_PACKAGE_NAME;
120         this.classLoaderInterface = classLoaderInterface;
121     }
122 
123     public JspCompilationContext(String tagfile,
124                                  TagInfo tagInfo,
125                                  Options options,
126                                  ServletContext context,
127                                  JspServletWrapper jsw,
128                                  JspRuntimeContext rctxt,
129                                  URL tagFileJarUrl) {
130         this(tagfile, false, options, context, jsw, rctxt, null);
131         this.isTagFile = true;
132         this.tagInfo = tagInfo;
133         this.tagFileJarUrl = tagFileJarUrl;
134         if (tagFileJarUrl != null) {
135             isPackagedTagFile = true;
136         }
137     }
138 
139     /* ==================== Methods to override ==================== */
140 
141     /*** ---------- Class path and loader ---------- */
142 
143     /***
144      * The classpath that is passed off to the Java compiler.
145      */
146     public String getClassPath() {
147         if (classPath != null)
148             return classPath;
149         return rctxt.getClassPath();
150     }
151 
152     /***
153      * The classpath that is passed off to the Java compiler.
154      */
155     public void setClassPath(String classPath) {
156         this.classPath = classPath;
157     }
158 
159     /***
160      * What class loader to use for loading classes while compiling
161      * this JSP?
162      */
163     public ClassLoader getClassLoader() {
164         if (loader != null)
165             return loader;
166         return rctxt.getParentClassLoader();
167     }
168 
169     public void setClassLoader(URLClassLoader loader) {
170         this.loader = loader;
171     }
172 
173     public ClassLoader getJspLoader() {
174         if (jspLoader == null) {
175             jspLoader = new JasperLoader
176                     (new URL[]{baseUrl},
177                             getClassLoader(),
178                             rctxt.getPermissionCollection(),
179                             rctxt.getCodeSource());
180         }
181         return jspLoader;
182     }
183 
184     /*** ---------- Input/Output  ---------- */
185 
186     /***
187      * Create a "Compiler" object based on some init param data. This
188      * is not done yet. Right now we're just hardcoding the actual
189      * compilers that are created.
190      */
191     public Compiler createCompiler() throws JasperException {
192         jspCompiler = new CustomCompiler();
193         jspCompiler.init(this, jsw);
194         return jspCompiler;
195     }
196 
197     private Compiler createCompiler(String className) {
198         Compiler compiler = null;
199         try {
200             compiler = (Compiler) Class.forName(className).newInstance();
201         } catch (Throwable t) {
202             if (log.isDebugEnabled()) {
203                 log.debug(Localizer.getMessage("jsp.error.compiler"), t);
204             }
205         }
206         return compiler;
207     }
208 
209     public Compiler getCompiler() {
210         return jspCompiler;
211     }
212 
213     /*** ---------- Access resources in the webapp ---------- */
214 
215     /***
216      * Gets a resource as a stream, relative to the meanings of this
217      * context's implementation.
218      *
219      * @return a null if the resource cannot be found or represented
220      *         as an InputStream.
221      */
222     public java.io.InputStream getResourceAsStream(String res) {
223         try {
224             return classLoaderInterface.getResourceAsStream(canonicalURI(StringUtils.removeStart(res, "/")));
225         } catch (IOException e) {
226             throw new RuntimeException(e);
227         }
228     }
229 
230 
231     public URL getResource(String res) throws MalformedURLException {
232         return classLoaderInterface.getResource(canonicalURI(StringUtils.removeStart(res, "/")));
233     }
234 
235     public Set getResourcePaths(String path) {
236         return context.getResourcePaths(canonicalURI(path));
237     }
238 
239     /***
240      * Gets the actual path of a URI relative to the context of
241      * the compilation.
242      */
243     public String getRealPath(String path) {
244         if (context != null) {
245             return context.getRealPath(path);
246         }
247         return path;
248     }
249 
250     /***
251      * Returns the tag-file-name-to-JAR-file map of this compilation unit,
252      * which maps tag file names to the JAR files in which the tag files are
253      * packaged.
254      * <p/>
255      * The map is populated when parsing the tag-file elements of the TLDs
256      * of any imported taglibs.
257      */
258     public Hashtable getTagFileJarUrls() {
259         return this.tagFileJarUrls;
260     }
261 
262     /***
263      * Returns the JAR file in which the tag file for which this
264      * JspCompilationContext was created is packaged, or null if this
265      * JspCompilationContext does not correspond to a tag file, or if the
266      * corresponding tag file is not packaged in a JAR.
267      */
268     public URL getTagFileJarUrl() {
269         return this.tagFileJarUrl;
270     }
271 
272     /* ==================== Common implementation ==================== */
273 
274     /***
275      * Just the class name (does not include package name) of the
276      * generated class.
277      */
278     public String getServletClassName() {
279 
280         if (className != null) {
281             return className;
282         }
283 
284         if (isTagFile) {
285             className = tagInfo.getTagClassName();
286             int lastIndex = className.lastIndexOf('.');
287             if (lastIndex != -1) {
288                 className = className.substring(lastIndex + 1);
289             }
290         } else {
291             int iSep = jspUri.lastIndexOf('/') + 1;
292             className = JspUtil.makeJavaIdentifier(jspUri.substring(iSep));
293         }
294         return className;
295     }
296 
297     public void setServletClassName(String className) {
298         this.className = className;
299     }
300 
301     /***
302      * Path of the JSP URI. Note that this is not a file name. This is
303      * the context rooted URI of the JSP file.
304      */
305     public String getJspFile() {
306         return jspUri;
307     }
308 
309     /***
310      * Are we processing something that has been declared as an
311      * errorpage?
312      */
313     public boolean isErrorPage() {
314         return isErrPage;
315     }
316 
317     public void setErrorPage(boolean isErrPage) {
318         this.isErrPage = isErrPage;
319     }
320 
321     public boolean isTagFile() {
322         return isTagFile;
323     }
324 
325     public TagInfo getTagInfo() {
326         return tagInfo;
327     }
328 
329     public void setTagInfo(TagInfo tagi) {
330         tagInfo = tagi;
331     }
332 
333     /***
334      * True if we are compiling a tag file in prototype mode.
335      * ie we only generate codes with class for the tag handler with empty
336      * method bodies.
337      */
338     public boolean isPrototypeMode() {
339         return protoTypeMode;
340     }
341 
342     public void setPrototypeMode(boolean pm) {
343         protoTypeMode = pm;
344     }
345 
346     /***
347      * Package name for the generated class is make up of the base package
348      * name, which is user settable, and the derived package name.  The
349      * derived package name directly mirrors the file heirachy of the JSP page.
350      */
351     public String getServletPackageName() {
352         if (isTagFile()) {
353             String className = tagInfo.getTagClassName();
354             int lastIndex = className.lastIndexOf('.');
355             String pkgName = "";
356             if (lastIndex != -1) {
357                 pkgName = className.substring(0, lastIndex);
358             }
359             return pkgName;
360         } else {
361             String dPackageName = getDerivedPackageName();
362             if (dPackageName.length() == 0) {
363                 return basePackageName;
364             }
365             return basePackageName + '.' + getDerivedPackageName();
366         }
367 
368     }
369 
370     private String getDerivedPackageName() {
371         if (derivedPackageName == null) {
372             int iSep = jspUri.lastIndexOf('/');
373             derivedPackageName = (iSep > 0) ?
374                     JspUtil.makeJavaPackage(jspUri.substring(0, iSep)) : "";
375         }
376         return derivedPackageName;
377     }
378 
379     /***
380      * The package name into which the servlet class is generated.
381      */
382     public void setServletPackageName(String servletPackageName) {
383         this.basePackageName = servletPackageName;
384     }
385 
386     /***
387      * Full path name of the Java file into which the servlet is being
388      * generated.
389      */
390     public String getServletJavaFileName() {
391 
392         if (servletJavaFileName == null) {
393             servletJavaFileName =
394                     getServletClassName() + ".java";
395         }
396         return servletJavaFileName;
397     }
398 
399     public void setServletJavaFileName(String servletJavaFileName) {
400         this.servletJavaFileName = servletJavaFileName;
401     }
402 
403     /***
404      * Get hold of the Options object for this context.
405      */
406     public Options getOptions() {
407         return options;
408     }
409 
410     public ServletContext getServletContext() {
411         return context;
412     }
413 
414     public JspRuntimeContext getRuntimeContext() {
415         return rctxt;
416     }
417 
418     /***
419      * Path of the Java file relative to the work directory.
420      */
421     public String getJavaPath() {
422 
423         if (javaPath != null) {
424             return javaPath;
425         }
426 
427         if (isTagFile()) {
428             String tagName = tagInfo.getTagClassName();
429             javaPath = tagName.replace('.', '/') + ".java";
430         } else {
431             javaPath = getServletPackageName().replace('.', '/') + '/' +
432                     getServletClassName() + ".java";
433         }
434         return javaPath;
435     }
436 
437     public String getClassFileName() {
438 
439         if (classFileName == null) {
440             classFileName = getServletClassName() + ".class";
441         }
442         return classFileName;
443     }
444 
445     /***
446      * Get the content type of this JSP.
447      * <p/>
448      * Content type includes content type and encoding.
449      */
450     public String getContentType() {
451         return contentType;
452     }
453 
454     public void setContentType(String contentType) {
455         this.contentType = contentType;
456     }
457 
458     /***
459      * Where is the servlet being generated?
460      */
461     public ServletWriter getWriter() {
462         return writer;
463     }
464 
465     public void setWriter(ServletWriter writer) {
466         this.writer = writer;
467     }
468 
469     /***
470      * Gets the 'location' of the TLD associated with the given taglib 'uri'.
471      *
472      * @return An array of two Strings: The first element denotes the real
473      *         path to the TLD. If the path to the TLD points to a jar file, then the
474      *         second element denotes the name of the TLD entry in the jar file.
475      *         Returns null if the given uri is not associated with any tag library
476      *         'exposed' in the web application.
477      */
478     public String[] getTldLocation(String uri) throws JasperException {
479         String[] location =
480                 getOptions().getTldLocationsCache().getLocation(uri);
481         return location;
482     }
483 
484     /***
485      * Are we keeping generated code around?
486      */
487     public boolean keepGenerated() {
488         return getOptions().getKeepGenerated();
489     }
490 
491     // ==================== Removal ==================== 
492 
493     public void incrementRemoved() {
494         if (removed > 1) {
495             jspCompiler.removeGeneratedFiles();
496             if (rctxt != null)
497                 rctxt.removeWrapper(jspUri);
498         }
499         removed++;
500     }
501 
502     public boolean isRemoved() {
503         if (removed > 1) {
504             return true;
505         }
506         return false;
507     }
508 
509     // ==================== Compile and reload ====================
510 
511     public void compile() throws JasperException, FileNotFoundException {
512         createCompiler();
513         if (isPackagedTagFile || jspCompiler.isOutDated()) {
514             try {
515                 jspLoader = null;
516                 jspCompiler.compile();
517                 jsw.setReload(true);
518                 jsw.setCompilationException(null);
519             } catch (JasperException ex) {
520                 // Cache compilation exception
521                 jsw.setCompilationException(ex);
522                 throw ex;
523             } catch (Exception ex) {
524                 ex.printStackTrace();
525                 JasperException je = new JasperException(
526                         Localizer.getMessage("jsp.error.unable.compile"),
527                         ex);
528                 // Cache compilation exception
529                 jsw.setCompilationException(je);
530                 throw je;
531             }
532         }
533     }
534 
535     public String getJspUri() {
536         return jspUri;
537     }
538 
539 // ==================== Manipulating the class ====================
540 
541     public Class load()
542             throws JasperException, FileNotFoundException {
543         try {
544             getJspLoader();
545 
546             String name;
547             if (isTagFile()) {
548                 name = tagInfo.getTagClassName();
549             } else {
550                 name = getServletPackageName() + "." + getServletClassName();
551             }
552             servletClass = jspLoader.loadClass(name);
553         } catch (ClassNotFoundException cex) {
554             throw new JasperException(Localizer.getMessage("jsp.error.unable.load"),
555                     cex);
556         } catch (Exception ex) {
557             throw new JasperException(Localizer.getMessage("jsp.error.unable.compile"),
558                     ex);
559         }
560         removed = 0;
561         return servletClass;
562     }
563 
564     // ==================== Private methods ==================== 
565 
566     static Object outputDirLock = new Object();
567 
568     private static final boolean isPathSeparator(char c) {
569         return (c == '/' || c == '//');
570     }
571 
572     public static final String canonicalURI(String s) {
573         if (s == null) return null;
574         StringBuffer result = new StringBuffer();
575         final int len = s.length();
576         int pos = 0;
577         while (pos < len) {
578             char c = s.charAt(pos);
579             if (isPathSeparator(c)) {
580                 /*
581                 * multiple path separators.
582                 * 'foo///bar' -> 'foo/bar'
583                 */
584                 while (pos + 1 < len && isPathSeparator(s.charAt(pos + 1))) {
585                     ++pos;
586                 }
587 
588                 if (pos + 1 < len && s.charAt(pos + 1) == '.') {
589                     /*
590                     * a single dot at the end of the path - we are done.
591                     */
592                     if (pos + 2 >= len) break;
593 
594                     switch (s.charAt(pos + 2)) {
595                         /*
596                         * self directory in path
597                         * foo/./bar -> foo/bar
598                         */
599                         case '/':
600                         case '//':
601                             pos += 2;
602                             continue;
603 
604                             /*
605                             * two dots in a path: go back one hierarchy.
606                             * foo/bar/../baz -> foo/baz
607                             */
608                         case '.':
609                             // only if we have exactly _two_ dots.
610                             if (pos + 3 < len && isPathSeparator(s.charAt(pos + 3))) {
611                                 pos += 3;
612                                 int separatorPos = result.length() - 1;
613                                 while (separatorPos >= 0 &&
614                                         !isPathSeparator(result
615                                                 .charAt(separatorPos))) {
616                                     --separatorPos;
617                                 }
618                                 if (separatorPos >= 0)
619                                     result.setLength(separatorPos);
620                                 continue;
621                             }
622                     }
623                 }
624             }
625             result.append(c);
626             ++pos;
627         }
628         return result.toString();
629     }
630 }
631