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