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.jasper;
19  
20  import java.io.File;
21  import java.io.FileNotFoundException;
22  import java.net.MalformedURLException;
23  import java.net.URL;
24  import java.net.URLClassLoader;
25  import java.util.HashMap;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import javax.servlet.ServletContext;
30  import javax.servlet.jsp.tagext.TagInfo;
31  
32  import org.apache.jasper.compiler.Compiler;
33  import org.apache.jasper.compiler.JspRuntimeContext;
34  import org.apache.jasper.compiler.JspUtil;
35  import org.apache.jasper.compiler.Localizer;
36  import org.apache.jasper.compiler.ServletWriter;
37  import org.apache.jasper.servlet.JasperLoader;
38  import org.apache.jasper.servlet.JspServletWrapper;
39  
40  /**
41   * A place holder for various things that are used through out the JSP
42   * engine. This is a per-request/per-context data structure. Some of
43   * the instance variables are set at different points.
44   *
45   * Most of the path-related stuff is here - mangling names, versions, dirs,
46   * loading resources and dealing with uris. 
47   *
48   * @author Anil K. Vijendran
49   * @author Harish Prabandham
50   * @author Pierre Delisle
51   * @author Costin Manolache
52   * @author Kin-man Chung
53   */
54  public class JspCompilationContext {
55  
56      protected org.apache.juli.logging.Log log =
57          org.apache.juli.logging.LogFactory.getLog(JspCompilationContext.class);
58  
59      protected Map<String, URL> tagFileJarUrls;
60      protected boolean isPackagedTagFile;
61  
62      protected String className;
63      protected String jspUri;
64      protected boolean isErrPage;
65      protected String basePackageName;
66      protected String derivedPackageName;
67      protected String servletJavaFileName;
68      protected String javaPath;
69      protected String classFileName;
70      protected String contentType;
71      protected ServletWriter writer;
72      protected Options options;
73      protected JspServletWrapper jsw;
74      protected Compiler jspCompiler;
75      protected String classPath;
76  
77      protected String baseURI;
78      protected String outputDir;
79      protected ServletContext context;
80      protected URLClassLoader loader;
81  
82      protected JspRuntimeContext rctxt;
83  
84      protected int removed = 0;
85  
86      protected URLClassLoader jspLoader;
87      protected URL baseUrl;
88      protected Class servletClass;
89  
90      protected boolean isTagFile;
91      protected boolean protoTypeMode;
92      protected TagInfo tagInfo;
93      protected URL tagFileJarUrl;
94  
95      // jspURI _must_ be relative to the context
96      public JspCompilationContext(String jspUri,
97                                   boolean isErrPage,
98                                   Options options,
99                                   ServletContext context,
100                                  JspServletWrapper jsw,
101                                  JspRuntimeContext rctxt) {
102 
103         this.jspUri = canonicalURI(jspUri);
104         this.isErrPage = isErrPage;
105         this.options = options;
106         this.jsw = jsw;
107         this.context = context;
108 
109         this.baseURI = jspUri.substring(0, jspUri.lastIndexOf('/') + 1);
110         // hack fix for resolveRelativeURI
111         if (baseURI == null) {
112             baseURI = "/";
113         } else if (baseURI.charAt(0) != '/') {
114             // strip the basde slash since it will be combined with the
115             // uriBase to generate a file
116             baseURI = "/" + baseURI;
117         }
118         if (baseURI.charAt(baseURI.length() - 1) != '/') {
119             baseURI += '/';
120         }
121 
122         this.rctxt = rctxt;
123         this.tagFileJarUrls = new HashMap<String, URL>();
124         this.basePackageName = Constants.JSP_PACKAGE_NAME;
125     }
126 
127     public JspCompilationContext(String tagfile,
128                                  TagInfo tagInfo, 
129                                  Options options,
130                                  ServletContext context,
131                                  JspServletWrapper jsw,
132                                  JspRuntimeContext rctxt,
133                                  URL tagFileJarUrl) {
134         this(tagfile, false, options, context, jsw, rctxt);
135         this.isTagFile = true;
136         this.tagInfo = tagInfo;
137         this.tagFileJarUrl = tagFileJarUrl;
138         if (tagFileJarUrl != null) {
139             isPackagedTagFile = true;
140         }
141     }
142 
143     /* ==================== Methods to override ==================== */
144     
145     /** ---------- Class path and loader ---------- */
146 
147     /**
148      * The classpath that is passed off to the Java compiler. 
149      */
150     public String getClassPath() {
151         if( classPath != null )
152             return classPath;
153         return rctxt.getClassPath();
154     }
155 
156     /**
157      * The classpath that is passed off to the Java compiler. 
158      */
159     public void setClassPath(String classPath) {
160         this.classPath = classPath;
161     }
162 
163     /**
164      * What class loader to use for loading classes while compiling
165      * this JSP?
166      */
167     public ClassLoader getClassLoader() {
168         if( loader != null )
169             return loader;
170         return rctxt.getParentClassLoader();
171     }
172 
173     public void setClassLoader(URLClassLoader loader) {
174         this.loader = loader;
175     }
176 
177     public ClassLoader getJspLoader() {
178         if( jspLoader == null ) {
179             jspLoader = new JasperLoader
180             (new URL[] {baseUrl},
181                     getClassLoader(),
182                     rctxt.getPermissionCollection(),
183                     rctxt.getCodeSource());
184         }
185         return jspLoader;
186     }
187 
188     /** ---------- Input/Output  ---------- */
189     
190     /**
191      * The output directory to generate code into.  The output directory
192      * is make up of the scratch directory, which is provide in Options,
193      * plus the directory derived from the package name.
194      */
195     public String getOutputDir() {
196 	if (outputDir == null) {
197 	    createOutputDir();
198 	}
199 
200         return outputDir;
201     }
202 
203     /**
204      * Create a "Compiler" object based on some init param data. This
205      * is not done yet. Right now we're just hardcoding the actual
206      * compilers that are created. 
207      */
208     public Compiler createCompiler() throws JasperException {
209         if (jspCompiler != null ) {
210             return jspCompiler;
211         }
212         jspCompiler = null;
213         if (options.getCompilerClassName() != null) {
214             jspCompiler = createCompiler(options.getCompilerClassName());
215         } else {
216             if (options.getCompiler() == null) {
217                 jspCompiler = createCompiler("org.apache.jasper.compiler.JDTCompiler");
218                 if (jspCompiler == null) {
219                     jspCompiler = createCompiler("org.apache.jasper.compiler.AntCompiler");
220                 }
221             } else {
222                 jspCompiler = createCompiler("org.apache.jasper.compiler.AntCompiler");
223                 if (jspCompiler == null) {
224                     jspCompiler = createCompiler("org.apache.jasper.compiler.JDTCompiler");
225                 }
226             }
227         }
228         if (jspCompiler == null) {
229             throw new IllegalStateException(Localizer.getMessage("jsp.error.compiler"));
230         }
231         jspCompiler.init(this, jsw);
232         return jspCompiler;
233     }
234 
235     protected Compiler createCompiler(String className) {
236         Compiler compiler = null; 
237         try {
238             compiler = (Compiler) Class.forName(className).newInstance();
239         } catch (InstantiationException e) {
240             log.warn(Localizer.getMessage("jsp.error.compiler"), e);
241         } catch (IllegalAccessException e) {
242             log.warn(Localizer.getMessage("jsp.error.compiler"), e);
243         } catch (NoClassDefFoundError e) {
244             if (log.isDebugEnabled()) {
245                 log.debug(Localizer.getMessage("jsp.error.compiler"), e);
246             }
247         } catch (ClassNotFoundException e) {
248             if (log.isDebugEnabled()) {
249                 log.debug(Localizer.getMessage("jsp.error.compiler"), e);
250             }
251         }
252         return compiler;
253     }
254     
255     public Compiler getCompiler() {
256         return jspCompiler;
257     }
258 
259     /** ---------- Access resources in the webapp ---------- */
260 
261     /** 
262      * Get the full value of a URI relative to this compilations context
263      * uses current file as the base.
264      */
265     public String resolveRelativeUri(String uri) {
266         // sometimes we get uri's massaged from File(String), so check for
267         // a root directory deperator char
268         if (uri.startsWith("/") || uri.startsWith(File.separator)) {
269             return uri;
270         } else {
271             return baseURI + uri;
272         }
273     }
274 
275     /**
276      * Gets a resource as a stream, relative to the meanings of this
277      * context's implementation.
278      * @return a null if the resource cannot be found or represented 
279      *         as an InputStream.
280      */
281     public java.io.InputStream getResourceAsStream(String res) {
282         return context.getResourceAsStream(canonicalURI(res));
283     }
284 
285 
286     public URL getResource(String res) throws MalformedURLException {
287         URL result = null;
288 
289         if (res.startsWith("/META-INF/")) {
290             // This is a tag file packaged in a jar that is being compiled
291             URL jarUrl = tagFileJarUrls.get(res);
292             if (jarUrl == null) {
293                 jarUrl = tagFileJarUrl;
294             }
295             if (jarUrl != null) {
296                 result = new URL(jarUrl.toExternalForm() + res.substring(1));
297             }
298         } else if (res.startsWith("jar:file:")) {
299                 // This is a tag file packaged in a jar that is being checked
300                 // for a dependency
301                 result = new URL(res);
302 
303         } else {
304             result = context.getResource(canonicalURI(res));
305         }
306         return result;
307     }
308 
309 
310     public Set getResourcePaths(String path) {
311         return context.getResourcePaths(canonicalURI(path));
312     }
313 
314     /** 
315      * Gets the actual path of a URI relative to the context of
316      * the compilation.
317      */
318     public String getRealPath(String path) {
319         if (context != null) {
320             return context.getRealPath(path);
321         }
322         return path;
323     }
324 
325     /**
326      * Returns the tag-file-name-to-JAR-file map of this compilation unit,
327      * which maps tag file names to the JAR files in which the tag files are
328      * packaged.
329      *
330      * The map is populated when parsing the tag-file elements of the TLDs
331      * of any imported taglibs. 
332      */
333     public URL getTagFileJarUrl(String tagFile) {
334         return this.tagFileJarUrls.get(tagFile);
335     }
336 
337     public void setTagFileJarUrl(String tagFile, URL tagFileURL) {
338         this.tagFileJarUrls.put(tagFile, tagFileURL);
339     }
340 
341     /**
342      * Returns the JAR file in which the tag file for which this
343      * JspCompilationContext was created is packaged, or null if this
344      * JspCompilationContext does not correspond to a tag file, or if the
345      * corresponding tag file is not packaged in a JAR.
346      */
347     public URL getTagFileJarUrl() {
348         return this.tagFileJarUrl;
349     }
350 
351     /* ==================== Common implementation ==================== */
352 
353     /**
354      * Just the class name (does not include package name) of the
355      * generated class. 
356      */
357     public String getServletClassName() {
358 
359         if (className != null) {
360             return className;
361         }
362 
363         if (isTagFile) {
364             className = tagInfo.getTagClassName();
365             int lastIndex = className.lastIndexOf('.');
366             if (lastIndex != -1) {
367                 className = className.substring(lastIndex + 1);
368             }
369         } else {
370             int iSep = jspUri.lastIndexOf('/') + 1;
371             className = JspUtil.makeJavaIdentifier(jspUri.substring(iSep));
372         }
373         return className;
374     }
375 
376     public void setServletClassName(String className) {
377         this.className = className;
378     }
379     
380     /**
381      * Path of the JSP URI. Note that this is not a file name. This is
382      * the context rooted URI of the JSP file. 
383      */
384     public String getJspFile() {
385         return jspUri;
386     }
387 
388     /**
389      * Are we processing something that has been declared as an
390      * errorpage? 
391      */
392     public boolean isErrorPage() {
393         return isErrPage;
394     }
395 
396     public void setErrorPage(boolean isErrPage) {
397         this.isErrPage = isErrPage;
398     }
399 
400     public boolean isTagFile() {
401         return isTagFile;
402     }
403 
404     public TagInfo getTagInfo() {
405         return tagInfo;
406     }
407 
408     public void setTagInfo(TagInfo tagi) {
409         tagInfo = tagi;
410     }
411 
412     /**
413      * True if we are compiling a tag file in prototype mode.
414      * ie we only generate codes with class for the tag handler with empty
415      * method bodies.
416      */
417     public boolean isPrototypeMode() {
418         return protoTypeMode;
419     }
420 
421     public void setPrototypeMode(boolean pm) {
422         protoTypeMode = pm;
423     }
424 
425     /**
426      * Package name for the generated class is make up of the base package
427      * name, which is user settable, and the derived package name.  The
428      * derived package name directly mirrors the file heirachy of the JSP page.
429      */
430     public String getServletPackageName() {
431         if (isTagFile()) {
432             String className = tagInfo.getTagClassName();
433             int lastIndex = className.lastIndexOf('.');
434             String pkgName = "";
435             if (lastIndex != -1) {
436                 pkgName = className.substring(0, lastIndex);
437             }
438             return pkgName;
439         } else {
440             String dPackageName = getDerivedPackageName();
441             if (dPackageName.length() == 0) {
442                 return basePackageName;
443             }
444             return basePackageName + '.' + getDerivedPackageName();
445         }
446     }
447 
448     protected String getDerivedPackageName() {
449         if (derivedPackageName == null) {
450             int iSep = jspUri.lastIndexOf('/');
451             derivedPackageName = (iSep > 0) ?
452                     JspUtil.makeJavaPackage(jspUri.substring(1,iSep)) : "";
453         }
454         return derivedPackageName;
455     }
456 	    
457     /**
458      * The package name into which the servlet class is generated.
459      */
460     public void setServletPackageName(String servletPackageName) {
461         this.basePackageName = servletPackageName;
462     }
463 
464     /**
465      * Full path name of the Java file into which the servlet is being
466      * generated. 
467      */
468     public String getServletJavaFileName() {
469         if (servletJavaFileName == null) {
470             servletJavaFileName = getOutputDir() + getServletClassName() + ".java";
471         }
472         return servletJavaFileName;
473     }
474 
475     /**
476      * Get hold of the Options object for this context. 
477      */
478     public Options getOptions() {
479         return options;
480     }
481 
482     public ServletContext getServletContext() {
483         return context;
484     }
485 
486     public JspRuntimeContext getRuntimeContext() {
487         return rctxt;
488     }
489 
490     /**
491      * Path of the Java file relative to the work directory.
492      */
493     public String getJavaPath() {
494 
495         if (javaPath != null) {
496             return javaPath;
497         }
498 
499         if (isTagFile()) {
500 	    String tagName = tagInfo.getTagClassName();
501             javaPath = tagName.replace('.', '/') + ".java";
502         } else {
503             javaPath = getServletPackageName().replace('.', '/') + '/' +
504                        getServletClassName() + ".java";
505 	}
506         return javaPath;
507     }
508 
509     public String getClassFileName() {
510         if (classFileName == null) {
511             classFileName = getOutputDir() + getServletClassName() + ".class";
512         }
513         return classFileName;
514     }
515 
516     /**
517      * Get the content type of this JSP.
518      *
519      * Content type includes content type and encoding.
520      */
521     public String getContentType() {
522         return contentType;
523     }
524 
525     public void setContentType(String contentType) {
526         this.contentType = contentType;
527     }
528 
529     /**
530      * Where is the servlet being generated?
531      */
532     public ServletWriter getWriter() {
533         return writer;
534     }
535 
536     public void setWriter(ServletWriter writer) {
537         this.writer = writer;
538     }
539 
540     /**
541      * Gets the 'location' of the TLD associated with the given taglib 'uri'.
542      * 
543      * @return An array of two Strings: The first element denotes the real
544      * path to the TLD. If the path to the TLD points to a jar file, then the
545      * second element denotes the name of the TLD entry in the jar file.
546      * Returns null if the given uri is not associated with any tag library
547      * 'exposed' in the web application.
548      */
549     public String[] getTldLocation(String uri) throws JasperException {
550         String[] location = 
551             getOptions().getTldLocationsCache().getLocation(uri);
552         return location;
553     }
554 
555     /**
556      * Are we keeping generated code around?
557      */
558     public boolean keepGenerated() {
559         return getOptions().getKeepGenerated();
560     }
561 
562     // ==================== Removal ==================== 
563 
564     public void incrementRemoved() {
565         if (removed == 0 && rctxt != null) {
566             rctxt.removeWrapper(jspUri);
567         }
568         removed++;
569     }
570 
571     public boolean isRemoved() {
572         if (removed > 1 ) {
573             return true;
574         }
575         return false;
576     }
577 
578     // ==================== Compile and reload ====================
579     
580     public void compile() throws JasperException, FileNotFoundException {
581         createCompiler();
582         if (jspCompiler.isOutDated()) {
583             try {
584                 jspCompiler.removeGeneratedFiles();
585                 jspLoader = null;
586                 jspCompiler.compile();
587                 jsw.setReload(true);
588                 jsw.setCompilationException(null);
589             } catch (JasperException ex) {
590                 // Cache compilation exception
591                 jsw.setCompilationException(ex);
592                 throw ex;
593             } catch (Exception ex) {
594                 JasperException je = new JasperException(
595                             Localizer.getMessage("jsp.error.unable.compile"),
596                             ex);
597                 // Cache compilation exception
598                 jsw.setCompilationException(je);
599                 throw je;
600             }
601         }
602     }
603 
604     // ==================== Manipulating the class ====================
605 
606     public Class load() 
607         throws JasperException, FileNotFoundException
608     {
609         try {
610             getJspLoader();
611             
612             String name = getFQCN();
613             servletClass = jspLoader.loadClass(name);
614         } catch (ClassNotFoundException cex) {
615             throw new JasperException(Localizer.getMessage("jsp.error.unable.load"),
616                                       cex);
617         } catch (Exception ex) {
618             throw new JasperException(Localizer.getMessage("jsp.error.unable.compile"),
619                                       ex);
620         }
621         removed = 0;
622         return servletClass;
623     }
624 
625     public String getFQCN() {
626         String name;
627         if (isTagFile()) {
628             name = tagInfo.getTagClassName();
629         } else {
630             name = getServletPackageName() + "." + getServletClassName();
631         }
632         return name;
633     }
634 
635     // ==================== protected methods ==================== 
636 
637     static Object outputDirLock = new Object();
638 
639     public void checkOutputDir() {
640         if (outputDir != null) {
641             if (!(new File(outputDir)).exists()) {
642                 makeOutputDir();
643             }
644         } else {
645             createOutputDir();
646         }
647     }
648         
649     protected boolean makeOutputDir() {
650         synchronized(outputDirLock) {
651             File outDirFile = new File(outputDir);
652             return (outDirFile.exists() || outDirFile.mkdirs());
653         }
654     }
655 
656     protected void createOutputDir() {
657         String path = null;
658         if (isTagFile()) {
659             String tagName = tagInfo.getTagClassName();
660             path = tagName.replace('.', File.separatorChar);
661             path = path.substring(0, path.lastIndexOf(File.separatorChar));
662         } else {
663             path = getServletPackageName().replace('.',File.separatorChar);
664         }
665 
666             // Append servlet or tag handler path to scratch dir
667             try {
668                 File base = options.getScratchDir();
669                 baseUrl = base.toURI().toURL();
670                 outputDir = base.getAbsolutePath() + File.separator + path + 
671                     File.separator;
672                 if (!makeOutputDir()) {
673                     throw new IllegalStateException(Localizer.getMessage("jsp.error.outputfolder"));
674                 }
675             } catch (MalformedURLException e) {
676                 throw new IllegalStateException(Localizer.getMessage("jsp.error.outputfolder"), e);
677             }
678     }
679     
680     protected static final boolean isPathSeparator(char c) {
681        return (c == '/' || c == '\\');
682     }
683 
684     protected static final String canonicalURI(String s) {
685        if (s == null) return null;
686        StringBuffer result = new StringBuffer();
687        final int len = s.length();
688        int pos = 0;
689        while (pos < len) {
690            char c = s.charAt(pos);
691            if ( isPathSeparator(c) ) {
692                /*
693                 * multiple path separators.
694                 * 'foo///bar' -> 'foo/bar'
695                 */
696                while (pos+1 < len && isPathSeparator(s.charAt(pos+1))) {
697                    ++pos;
698                }
699 
700                if (pos+1 < len && s.charAt(pos+1) == '.') {
701                    /*
702                     * a single dot at the end of the path - we are done.
703                     */
704                    if (pos+2 >= len) break;
705 
706                    switch (s.charAt(pos+2)) {
707                        /*
708                         * self directory in path
709                         * foo/./bar -> foo/bar
710                         */
711                    case '/':
712                    case '\\':
713                        pos += 2;
714                        continue;
715 
716                        /*
717                         * two dots in a path: go back one hierarchy.
718                         * foo/bar/../baz -> foo/baz
719                         */
720                    case '.':
721                        // only if we have exactly _two_ dots.
722                        if (pos+3 < len && isPathSeparator(s.charAt(pos+3))) {
723                            pos += 3;
724                            int separatorPos = result.length()-1;
725                            while (separatorPos >= 0 && 
726                                   ! isPathSeparator(result
727                                                     .charAt(separatorPos))) {
728                                --separatorPos;
729                            }
730                            if (separatorPos >= 0)
731                                result.setLength(separatorPos);
732                            continue;
733                        }
734                    }
735                }
736            }
737            result.append(c);
738            ++pos;
739        }
740        return result.toString();
741     }
742 }
743