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 package org.apache.jasper;
18
19 import java.io.File;
20 import java.io.FileNotFoundException;
21 import java.io.IOException;
22 import java.net.JarURLConnection;
23 import java.net.MalformedURLException;
24 import java.net.URL;
25 import java.net.URLClassLoader;
26 import java.net.URLConnection;
27 import java.util.Set;
28 import java.util.jar.JarEntry;
29
30 import javax.servlet.ServletContext;
31 import javax.servlet.jsp.tagext.TagInfo;
32
33 import org.apache.jasper.compiler.Compiler;
34 import org.apache.jasper.compiler.JspRuntimeContext;
35 import org.apache.jasper.compiler.JspUtil;
36 import org.apache.jasper.compiler.Localizer;
37 import org.apache.jasper.compiler.ServletWriter;
38 import org.apache.jasper.servlet.JasperLoader;
39 import org.apache.jasper.servlet.JspServletWrapper;
40 import org.apache.juli.logging.Log;
41 import org.apache.juli.logging.LogFactory;
42 import org.apache.tomcat.Jar;
43 import org.apache.tomcat.util.descriptor.tld.TldResourcePath;
44
45 /**
46  * A place holder for various things that are used through out the JSP
47  * engine. This is a per-request/per-context data structure. Some of
48  * the instance variables are set at different points.
49  *
50  * Most of the path-related stuff is here - mangling names, versions, dirs,
51  * loading resources and dealing with uris.
52  *
53  * @author Anil K. Vijendran
54  * @author Harish Prabandham
55  * @author Pierre Delisle
56  * @author Costin Manolache
57  * @author Kin-man Chung
58  */

59 public class JspCompilationContext {
60
61     private final Log log = LogFactory.getLog(JspCompilationContext.class); // must not be static
62
63     private String className;
64     private final String jspUri;
65     private String basePackageName;
66     private String derivedPackageName;
67     private String servletJavaFileName;
68     private String javaPath;
69     private String classFileName;
70     private ServletWriter writer;
71     private final Options options;
72     private final JspServletWrapper jsw;
73     private Compiler jspCompiler;
74     private String classPath;
75
76     private final String baseURI;
77     private String outputDir;
78     private final ServletContext context;
79     private ClassLoader loader;
80
81     private final JspRuntimeContext rctxt;
82
83     private volatile boolean removed = false;
84
85     // volatile so changes are visible when multiple threads request a JSP file
86     // that has been modified
87     private volatile URLClassLoader jspLoader;
88     private URL baseUrl;
89     private Class<?> servletClass;
90
91     private final boolean isTagFile;
92     private boolean protoTypeMode;
93     private TagInfo tagInfo;
94     private Jar tagJar;
95
96     // jspURI _must_ be relative to the context
97     public JspCompilationContext(String jspUri, Options options,
98             ServletContext context, JspServletWrapper jsw,
99             JspRuntimeContext rctxt) {
100         this(jspUri, null, options, context, jsw, rctxt, nullfalse);
101     }
102
103     public JspCompilationContext(String tagfile, TagInfo tagInfo,
104             Options options, ServletContext context, JspServletWrapper jsw,
105             JspRuntimeContext rctxt, Jar tagJar) {
106         this(tagfile, tagInfo, options, context, jsw, rctxt, tagJar, true);
107     }
108
109     private JspCompilationContext(String jspUri, TagInfo tagInfo,
110             Options options, ServletContext context, JspServletWrapper jsw,
111             JspRuntimeContext rctxt, Jar tagJar, boolean isTagFile) {
112
113         this.jspUri = canonicalURI(jspUri);
114         this.options = options;
115         this.jsw = jsw;
116         this.context = context;
117
118         String baseURI = jspUri.substring(0, jspUri.lastIndexOf('/') + 1);
119         // hack fix for resolveRelativeURI
120         if (baseURI.isEmpty()) {
121             baseURI = "/";
122         } else if (baseURI.charAt(0) != '/') {
123             // strip the base slash since it will be combined with the
124             // uriBase to generate a file
125             baseURI = "/" + baseURI;
126         }
127         if (baseURI.charAt(baseURI.length() - 1) != '/') {
128             baseURI += '/';
129         }
130         this.baseURI = baseURI;
131
132         this.rctxt = rctxt;
133         this.basePackageName = Constants.JSP_PACKAGE_NAME;
134
135         this.tagInfo = tagInfo;
136         this.tagJar = tagJar;
137         this.isTagFile = isTagFile;
138     }
139
140
141     /* ==================== Methods to override ==================== */
142
143     /** ---------- Class path and loader ---------- */
144
145     /**
146      * @return the classpath that is passed off to the Java compiler.
147      */

148     public String getClassPath() {
149         if( classPath != null ) {
150             return classPath;
151         }
152         return rctxt.getClassPath();
153     }
154
155     /**
156      * The classpath that is passed off to the Java compiler.
157      * @param classPath The class path to use
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      * @return the class loader used to load all compiled classes
167      */

168     public ClassLoader getClassLoader() {
169         if( loader != null ) {
170             return loader;
171         }
172         return rctxt.getParentClassLoader();
173     }
174
175     public void setClassLoader(ClassLoader loader) {
176         this.loader = loader;
177     }
178
179     public ClassLoader getJspLoader() {
180         if( jspLoader == null ) {
181             jspLoader = new JasperLoader
182                     (new URL[] {baseUrl},
183                             getClassLoader(),
184                             rctxt.getPermissionCollection());
185         }
186         return jspLoader;
187     }
188
189     public void clearJspLoader() {
190         jspLoader = null;
191     }
192
193
194     /** ---------- Input/Output  ---------- */
195
196     /**
197      * The output directory to generate code into.  The output directory
198      * is make up of the scratch directory, which is provide in Options,
199      * plus the directory derived from the package name.
200      * @return the output directory in which the generated sources are placed
201      */

202     public String getOutputDir() {
203         if (outputDir == null) {
204             createOutputDir();
205         }
206
207         return outputDir;
208     }
209
210     /**
211      * Create a "Compiler" object based on some init param data. This
212      * is not done yet. Right now we're just hardcoding the actual
213      * compilers that are created.
214      * @return the Java compiler wrapper
215      */

216     public Compiler createCompiler() {
217         if (jspCompiler != null ) {
218             return jspCompiler;
219         }
220         jspCompiler = null;
221         if (options.getCompilerClassName() != null) {
222             jspCompiler = createCompiler(options.getCompilerClassName());
223         } else {
224             if (options.getCompiler() == null) {
225                 jspCompiler = createCompiler("org.apache.jasper.compiler.JDTCompiler");
226                 if (jspCompiler == null) {
227                     jspCompiler = createCompiler("org.apache.jasper.compiler.AntCompiler");
228                 }
229             } else {
230                 jspCompiler = createCompiler("org.apache.jasper.compiler.AntCompiler");
231                 if (jspCompiler == null) {
232                     jspCompiler = createCompiler("org.apache.jasper.compiler.JDTCompiler");
233                 }
234             }
235         }
236         if (jspCompiler == null) {
237             throw new IllegalStateException(Localizer.getMessage("jsp.error.compiler.config",
238                     options.getCompilerClassName(), options.getCompiler()));
239         }
240         jspCompiler.init(this, jsw);
241         return jspCompiler;
242     }
243
244     protected Compiler createCompiler(String className) {
245         Compiler compiler = null;
246         try {
247             compiler = (Compiler) Class.forName(className).getConstructor().newInstance();
248         } catch (NoClassDefFoundError | ClassNotFoundException e) {
249             if (log.isDebugEnabled()) {
250                 log.debug(Localizer.getMessage("jsp.error.compiler"), e);
251             }
252         } catch (ReflectiveOperationException e) {
253             log.warn(Localizer.getMessage("jsp.error.compiler"), e);
254         }
255         return compiler;
256     }
257
258     public Compiler getCompiler() {
259         return jspCompiler;
260     }
261
262     /** ---------- Access resources in the webapp ---------- */
263
264     /**
265      * Get the full value of a URI relative to this compilations context
266      * uses current file as the base.
267      * @param uri The relative URI
268      * @return absolute URI
269      */

270     public String resolveRelativeUri(String uri) {
271         // sometimes we get uri's massaged from File(String), so check for
272         // a root directory separator char
273         if (uri.startsWith("/") || uri.startsWith(File.separator)) {
274             return uri;
275         } else {
276             return baseURI + uri;
277         }
278     }
279
280     /**
281      * Gets a resource as a stream, relative to the meanings of this
282      * context's implementation.
283      * @param res the resource to look for
284      * @return a null if the resource cannot be found or represented
285      *         as an InputStream.
286      */

287     public java.io.InputStream getResourceAsStream(String res) {
288         return context.getResourceAsStream(canonicalURI(res));
289     }
290
291
292     public URL getResource(String res) throws MalformedURLException {
293         return context.getResource(canonicalURI(res));
294     }
295
296
297     public Set<String> getResourcePaths(String path) {
298         return context.getResourcePaths(canonicalURI(path));
299     }
300
301     /**
302      * Gets the actual path of a URI relative to the context of
303      * the compilation.
304      * @param path The webapp path
305      * @return the corresponding path in the filesystem
306      */

307     public String getRealPath(String path) {
308         if (context != null) {
309             return context.getRealPath(path);
310         }
311         return path;
312     }
313
314     /**
315      * Returns the JAR file in which the tag file for which this
316      * JspCompilationContext was created is packaged, or null if this
317      * JspCompilationContext does not correspond to a tag file, or if the
318      * corresponding tag file is not packaged in a JAR.
319      * @return a JAR file
320      */

321     public Jar getTagFileJar() {
322         return this.tagJar;
323     }
324
325     public void setTagFileJar(Jar tagJar) {
326         this.tagJar = tagJar;
327     }
328
329     /* ==================== Common implementation ==================== */
330
331     /**
332      * Just the class name (does not include package name) of the
333      * generated class.
334      * @return the class name
335      */

336     public String getServletClassName() {
337
338         if (className != null) {
339             return className;
340         }
341
342         if (isTagFile) {
343             className = tagInfo.getTagClassName();
344             int lastIndex = className.lastIndexOf('.');
345             if (lastIndex != -1) {
346                 className = className.substring(lastIndex + 1);
347             }
348         } else {
349             int iSep = jspUri.lastIndexOf('/') + 1;
350             className = JspUtil.makeJavaIdentifier(jspUri.substring(iSep));
351         }
352         return className;
353     }
354
355     public void setServletClassName(String className) {
356         this.className = className;
357     }
358
359     /**
360      * Path of the JSP URI. Note that this is not a file name. This is
361      * the context rooted URI of the JSP file.
362      * @return the path to the JSP
363      */

364     public String getJspFile() {
365         return jspUri;
366     }
367
368
369     public Long getLastModified(String resource) {
370         return getLastModified(resource, tagJar);
371     }
372
373
374     public Long getLastModified(String resource, Jar tagJar) {
375         long result = -1;
376         URLConnection uc = null;
377         try {
378             if (tagJar != null) {
379                 if (resource.startsWith("/")) {
380                     resource = resource.substring(1);
381                 }
382                 result = tagJar.getLastModified(resource);
383             } else {
384                 URL jspUrl = getResource(resource);
385                 if (jspUrl == null) {
386                     incrementRemoved();
387                     return Long.valueOf(result);
388                 }
389                 uc = jspUrl.openConnection();
390                 if (uc instanceof JarURLConnection) {
391                     JarEntry jarEntry = ((JarURLConnection) uc).getJarEntry();
392                     if (jarEntry != null) {
393                         result = jarEntry.getTime();
394                     } else {
395                         result = uc.getLastModified();
396                     }
397                 } else {
398                     result = uc.getLastModified();
399                 }
400             }
401         } catch (IOException e) {
402             if (log.isDebugEnabled()) {
403                 log.debug(Localizer.getMessage(
404                         "jsp.error.lastModified", getJspFile()), e);
405             }
406             result = -1;
407         } finally {
408             if (uc != null) {
409                 try {
410                     uc.getInputStream().close();
411                 } catch (IOException e) {
412                     if (log.isDebugEnabled()) {
413                         log.debug(Localizer.getMessage(
414                                 "jsp.error.lastModified", getJspFile()), e);
415                     }
416                     result = -1;
417                 }
418             }
419         }
420         return Long.valueOf(result);
421     }
422
423     public boolean isTagFile() {
424         return isTagFile;
425     }
426
427     public TagInfo getTagInfo() {
428         return tagInfo;
429     }
430
431     public void setTagInfo(TagInfo tagi) {
432         tagInfo = tagi;
433     }
434
435     /**
436      * @return <code>true</code> if we are compiling a tag file
437      *  in prototype mode.
438      *  ie we only generate codes with class for the tag handler with empty
439      *  method bodies.
440      */

441     public boolean isPrototypeMode() {
442         return protoTypeMode;
443     }
444
445     public void setPrototypeMode(boolean pm) {
446         protoTypeMode = pm;
447     }
448
449     /**
450      * Package name for the generated class is made up of the base package
451      * name, which is user settable, and the derived package name.  The
452      * derived package name directly mirrors the file hierarchy of the JSP page.
453      * @return the package name
454      */

455     public String getServletPackageName() {
456         if (isTagFile()) {
457             String className = tagInfo.getTagClassName();
458             int lastIndex = className.lastIndexOf('.');
459             String packageName = "";
460             if (lastIndex != -1) {
461                 packageName = className.substring(0, lastIndex);
462             }
463             return packageName;
464         } else {
465             String dPackageName = getDerivedPackageName();
466             if (dPackageName.length() == 0) {
467                 return basePackageName;
468             }
469             return basePackageName + '.' + getDerivedPackageName();
470         }
471     }
472
473     protected String getDerivedPackageName() {
474         if (derivedPackageName == null) {
475             int iSep = jspUri.lastIndexOf('/');
476             derivedPackageName = (iSep > 0) ?
477                     JspUtil.makeJavaPackage(jspUri.substring(1,iSep)) : "";
478         }
479         return derivedPackageName;
480     }
481
482     /**
483      * @return The base package name into which all servlet and associated code
484      *         is generated
485      */

486     public String getBasePackageName() {
487         return basePackageName;
488     }
489
490     /**
491      * The package name into which the servlet class is generated.
492      * @param basePackageName The package name to use
493      */

494     public void setBasePackageName(String basePackageName) {
495         this.basePackageName = basePackageName;
496     }
497
498     /**
499      * @return Full path name of the Java file into which the servlet is being
500      * generated.
501      */

502     public String getServletJavaFileName() {
503         if (servletJavaFileName == null) {
504             servletJavaFileName = getOutputDir() + getServletClassName() + ".java";
505         }
506         return servletJavaFileName;
507     }
508
509     /**
510      * @return the Options object for this context.
511      */

512     public Options getOptions() {
513         return options;
514     }
515
516     public ServletContext getServletContext() {
517         return context;
518     }
519
520     public JspRuntimeContext getRuntimeContext() {
521         return rctxt;
522     }
523
524     /**
525      * @return the path of the Java file relative to the work directory.
526      */

527     public String getJavaPath() {
528
529         if (javaPath != null) {
530             return javaPath;
531         }
532
533         if (isTagFile()) {
534             String tagName = tagInfo.getTagClassName();
535             javaPath = tagName.replace('.', '/') + ".java";
536         } else {
537             javaPath = getServletPackageName().replace('.', '/') + '/' +
538                     getServletClassName() + ".java";
539         }
540         return javaPath;
541     }
542
543     public String getClassFileName() {
544         if (classFileName == null) {
545             classFileName = getOutputDir() + getServletClassName() + ".class";
546         }
547         return classFileName;
548     }
549
550     /**
551      * @return the writer that is used to write the generated Servlet source.
552      */

553     public ServletWriter getWriter() {
554         return writer;
555     }
556
557     public void setWriter(ServletWriter writer) {
558         this.writer = writer;
559     }
560
561     /**
562      * Gets the 'location' of the TLD associated with the given taglib 'uri'.
563      * @param uri The taglib URI
564      * @return An array of two Strings: The first element denotes the real
565      * path to the TLD. If the path to the TLD points to a jar file, then the
566      * second element denotes the name of the TLD entry in the jar file.
567      * Returns null if the given uri is not associated with any tag library
568      * 'exposed' in the web application.
569      */

570     public TldResourcePath getTldResourcePath(String uri) {
571         return getOptions().getTldCache().getTldResourcePath(uri);
572     }
573
574     /**
575      * @return <code>true</code> if generated code is kept.
576      */

577     public boolean keepGenerated() {
578         return getOptions().getKeepGenerated();
579     }
580
581     // ==================== Removal ====================
582
583     public void incrementRemoved() {
584         if (removed == false && rctxt != null) {
585             rctxt.removeWrapper(jspUri);
586         }
587         removed = true;
588     }
589
590     public boolean isRemoved() {
591         return removed;
592     }
593
594     // ==================== Compile and reload ====================
595
596     public void compile() throws JasperException, FileNotFoundException {
597         createCompiler();
598         if (jspCompiler.isOutDated()) {
599             if (isRemoved()) {
600                 throw new FileNotFoundException(jspUri);
601             }
602             try {
603                 jspCompiler.removeGeneratedFiles();
604                 jspLoader = null;
605                 jspCompiler.compile();
606                 jsw.setReload(true);
607                 jsw.setCompilationException(null);
608             } catch (JasperException ex) {
609                 // Cache compilation exception
610                 jsw.setCompilationException(ex);
611                 if (options.getDevelopment() && options.getRecompileOnFail()) {
612                     // Force a recompilation attempt on next access
613                     jsw.setLastModificationTest(-1);
614                 }
615                 throw ex;
616             } catch (FileNotFoundException fnfe) {
617                 // Re-throw to let caller handle this - will result in a 404
618                 throw fnfe;
619             } catch (Exception ex) {
620                 JasperException je = new JasperException(
621                         Localizer.getMessage("jsp.error.unable.compile"),
622                         ex);
623                 // Cache compilation exception
624                 jsw.setCompilationException(je);
625                 throw je;
626             }
627         }
628     }
629
630     // ==================== Manipulating the class ====================
631
632     public Class<?> load() throws JasperException {
633         try {
634             getJspLoader();
635
636             String name = getFQCN();
637             servletClass = jspLoader.loadClass(name);
638         } catch (ClassNotFoundException cex) {
639             throw new JasperException(Localizer.getMessage("jsp.error.unable.load"),
640                     cex);
641         } catch (Exception ex) {
642             throw new JasperException(Localizer.getMessage("jsp.error.unable.compile"),
643                     ex);
644         }
645         removed = false;
646         return servletClass;
647     }
648
649     public String getFQCN() {
650         String name;
651         if (isTagFile()) {
652             name = tagInfo.getTagClassName();
653         } else {
654             name = getServletPackageName() + "." + getServletClassName();
655         }
656         return name;
657     }
658
659     // ==================== protected methods ====================
660
661     private static final Object outputDirLock = new Object();
662
663     public void checkOutputDir() {
664         if (outputDir != null) {
665             if (!(new File(outputDir)).exists()) {
666                 makeOutputDir();
667             }
668         } else {
669             createOutputDir();
670         }
671     }
672
673     protected boolean makeOutputDir() {
674         synchronized(outputDirLock) {
675             File outDirFile = new File(outputDir);
676             return (outDirFile.mkdirs() || outDirFile.isDirectory());
677         }
678     }
679
680     protected void createOutputDir() {
681         String path = null;
682         if (isTagFile()) {
683             String tagName = tagInfo.getTagClassName();
684             path = tagName.replace('.', File.separatorChar);
685             path = path.substring(0, path.lastIndexOf(File.separatorChar));
686         } else {
687             path = getServletPackageName().replace('.',File.separatorChar);
688         }
689
690         // Append servlet or tag handler path to scratch dir
691         try {
692             File base = options.getScratchDir();
693             baseUrl = base.toURI().toURL();
694             outputDir = base.getAbsolutePath() + File.separator + path +
695                     File.separator;
696             if (!makeOutputDir()) {
697                 throw new IllegalStateException(Localizer.getMessage("jsp.error.outputfolder"));
698             }
699         } catch (MalformedURLException e) {
700             throw new IllegalStateException(Localizer.getMessage("jsp.error.outputfolder"), e);
701         }
702     }
703
704     protected static final boolean isPathSeparator(char c) {
705         return (c == '/' || c == '\\');
706     }
707
708     protected static final String canonicalURI(String s) {
709         if (s == null) {
710             return null;
711         }
712         StringBuilder result = new StringBuilder();
713         final int len = s.length();
714         int pos = 0;
715         while (pos < len) {
716             char c = s.charAt(pos);
717             if ( isPathSeparator(c) ) {
718                 /*
719                  * multiple path separators.
720                  * 'foo///bar' -> 'foo/bar'
721                  */

722                 while (pos+1 < len && isPathSeparator(s.charAt(pos+1))) {
723                     ++pos;
724                 }
725
726                 if (pos+1 < len && s.charAt(pos+1) == '.') {
727                     /*
728                      * a single dot at the end of the path - we are done.
729                      */

730                     if (pos+2 >= len) {
731                         break;
732                     }
733
734                     switch (s.charAt(pos+2)) {
735                         /*
736                          * self directory in path
737                          * foo/./bar -> foo/bar
738                          */

739                         case '/':
740                         case '\\':
741                             pos += 2;
742                             continue;
743
744                             /*
745                              * two dots in a path: go back one hierarchy.
746                              * foo/bar/../baz -> foo/baz
747                              */

748                         case '.':
749                             // only if we have exactly _two_ dots.
750                             if (pos+3 < len && isPathSeparator(s.charAt(pos+3))) {
751                                 pos += 3;
752                                 int separatorPos = result.length()-1;
753                                 while (separatorPos >= 0 &&
754                                         ! isPathSeparator(result
755                                                 .charAt(separatorPos))) {
756                                     --separatorPos;
757                                 }
758                                 if (separatorPos >= 0) {
759                                     result.setLength(separatorPos);
760                                 }
761                                 continue;
762                         }
763                     }
764                 }
765             }
766             result.append(c);
767             ++pos;
768         }
769         return result.toString();
770     }
771 }
772