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 volatile 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 > 0 ) {
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 if (isRemoved()) {
584 throw new FileNotFoundException(jspUri);
585 }
586 try {
587 jspCompiler.removeGeneratedFiles();
588 jspLoader = null;
589 jspCompiler.compile();
590 jsw.setReload(true);
591 jsw.setCompilationException(null);
592 } catch (JasperException ex) {
593 // Cache compilation exception
594 jsw.setCompilationException(ex);
595 throw ex;
596 } catch (Exception ex) {
597 JasperException je = new JasperException(
598 Localizer.getMessage("jsp.error.unable.compile"),
599 ex);
600 // Cache compilation exception
601 jsw.setCompilationException(je);
602 throw je;
603 }
604 }
605 }
606
607 // ==================== Manipulating the class ====================
608
609 public Class load()
610 throws JasperException, FileNotFoundException
611 {
612 try {
613 getJspLoader();
614
615 String name;
616 if (isTagFile()) {
617 name = tagInfo.getTagClassName();
618 } else {
619 name = getServletPackageName() + "." + getServletClassName();
620 }
621 servletClass = jspLoader.loadClass(name);
622 } catch (ClassNotFoundException cex) {
623 throw new JasperException(Localizer.getMessage("jsp.error.unable.load"),
624 cex);
625 } catch (Exception ex) {
626 throw new JasperException(Localizer.getMessage("jsp.error.unable.compile"),
627 ex);
628 }
629 removed = 0;
630 return servletClass;
631 }
632
633 // ==================== protected methods ====================
634
635 static Object outputDirLock = new Object();
636
637 public void checkOutputDir() {
638 if (outputDir != null) {
639 if (!(new File(outputDir)).exists()) {
640 makeOutputDir();
641 }
642 } else {
643 createOutputDir();
644 }
645 }
646
647 protected boolean makeOutputDir() {
648 synchronized(outputDirLock) {
649 File outDirFile = new File(outputDir);
650 return (outDirFile.exists() || outDirFile.mkdirs());
651 }
652 }
653
654 protected void createOutputDir() {
655 String path = null;
656 if (isTagFile()) {
657 String tagName = tagInfo.getTagClassName();
658 path = tagName.replace('.', File.separatorChar);
659 path = path.substring(0, path.lastIndexOf(File.separatorChar));
660 } else {
661 path = getServletPackageName().replace('.',File.separatorChar);
662 }
663
664 // Append servlet or tag handler path to scratch dir
665 try {
666 File base = options.getScratchDir();
667 baseUrl = base.toURI().toURL();
668 outputDir = base.getAbsolutePath() + File.separator + path +
669 File.separator;
670 if (!makeOutputDir()) {
671 throw new IllegalStateException(Localizer.getMessage("jsp.error.outputfolder"));
672 }
673 } catch (MalformedURLException e) {
674 throw new IllegalStateException(Localizer.getMessage("jsp.error.outputfolder"), e);
675 }
676 }
677
678 protected static final boolean isPathSeparator(char c) {
679 return (c == '/' || c == '\\');
680 }
681
682 protected static final String canonicalURI(String s) {
683 if (s == null) return null;
684 StringBuffer result = new StringBuffer();
685 final int len = s.length();
686 int pos = 0;
687 while (pos < len) {
688 char c = s.charAt(pos);
689 if ( isPathSeparator(c) ) {
690 /*
691 * multiple path separators.
692 * 'foo///bar' -> 'foo/bar'
693 */
694 while (pos+1 < len && isPathSeparator(s.charAt(pos+1))) {
695 ++pos;
696 }
697
698 if (pos+1 < len && s.charAt(pos+1) == '.') {
699 /*
700 * a single dot at the end of the path - we are done.
701 */
702 if (pos+2 >= len) break;
703
704 switch (s.charAt(pos+2)) {
705 /*
706 * self directory in path
707 * foo/./bar -> foo/bar
708 */
709 case '/':
710 case '\\':
711 pos += 2;
712 continue;
713
714 /*
715 * two dots in a path: go back one hierarchy.
716 * foo/bar/../baz -> foo/baz
717 */
718 case '.':
719 // only if we have exactly _two_ dots.
720 if (pos+3 < len && isPathSeparator(s.charAt(pos+3))) {
721 pos += 3;
722 int separatorPos = result.length()-1;
723 while (separatorPos >= 0 &&
724 ! isPathSeparator(result
725 .charAt(separatorPos))) {
726 --separatorPos;
727 }
728 if (separatorPos >= 0)
729 result.setLength(separatorPos);
730 continue;
731 }
732 }
733 }
734 }
735 result.append(c);
736 ++pos;
737 }
738 return result.toString();
739 }
740 }
741