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 return context.getResource(canonicalURI(res));
288 }
289
290 public Set getResourcePaths(String path) {
291 return context.getResourcePaths(canonicalURI(path));
292 }
293
294 /**
295 * Gets the actual path of a URI relative to the context of
296 * the compilation.
297 */
298 public String getRealPath(String path) {
299 if (context != null) {
300 return context.getRealPath(path);
301 }
302 return path;
303 }
304
305 /**
306 * Returns the tag-file-name-to-JAR-file map of this compilation unit,
307 * which maps tag file names to the JAR files in which the tag files are
308 * packaged.
309 *
310 * The map is populated when parsing the tag-file elements of the TLDs
311 * of any imported taglibs.
312 */
313 public URL getTagFileJarUrl(String tagFile) {
314 return this.tagFileJarUrls.get(tagFile);
315 }
316
317 public void setTagFileJarUrl(String tagFile, URL tagFileURL) {
318 this.tagFileJarUrls.put(tagFile, tagFileURL);
319 }
320
321 /**
322 * Returns the JAR file in which the tag file for which this
323 * JspCompilationContext was created is packaged, or null if this
324 * JspCompilationContext does not correspond to a tag file, or if the
325 * corresponding tag file is not packaged in a JAR.
326 */
327 public URL getTagFileJarUrl() {
328 return this.tagFileJarUrl;
329 }
330
331 /* ==================== Common implementation ==================== */
332
333 /**
334 * Just the class name (does not include package name) of the
335 * generated class.
336 */
337 public String getServletClassName() {
338
339 if (className != null) {
340 return className;
341 }
342
343 if (isTagFile) {
344 className = tagInfo.getTagClassName();
345 int lastIndex = className.lastIndexOf('.');
346 if (lastIndex != -1) {
347 className = className.substring(lastIndex + 1);
348 }
349 } else {
350 int iSep = jspUri.lastIndexOf('/') + 1;
351 className = JspUtil.makeJavaIdentifier(jspUri.substring(iSep));
352 }
353 return className;
354 }
355
356 public void setServletClassName(String className) {
357 this.className = className;
358 }
359
360 /**
361 * Path of the JSP URI. Note that this is not a file name. This is
362 * the context rooted URI of the JSP file.
363 */
364 public String getJspFile() {
365 return jspUri;
366 }
367
368 /**
369 * Are we processing something that has been declared as an
370 * errorpage?
371 */
372 public boolean isErrorPage() {
373 return isErrPage;
374 }
375
376 public void setErrorPage(boolean isErrPage) {
377 this.isErrPage = isErrPage;
378 }
379
380 public boolean isTagFile() {
381 return isTagFile;
382 }
383
384 public TagInfo getTagInfo() {
385 return tagInfo;
386 }
387
388 public void setTagInfo(TagInfo tagi) {
389 tagInfo = tagi;
390 }
391
392 /**
393 * True if we are compiling a tag file in prototype mode.
394 * ie we only generate codes with class for the tag handler with empty
395 * method bodies.
396 */
397 public boolean isPrototypeMode() {
398 return protoTypeMode;
399 }
400
401 public void setPrototypeMode(boolean pm) {
402 protoTypeMode = pm;
403 }
404
405 /**
406 * Package name for the generated class is make up of the base package
407 * name, which is user settable, and the derived package name. The
408 * derived package name directly mirrors the file heirachy of the JSP page.
409 */
410 public String getServletPackageName() {
411 if (isTagFile()) {
412 String className = tagInfo.getTagClassName();
413 int lastIndex = className.lastIndexOf('.');
414 String pkgName = "";
415 if (lastIndex != -1) {
416 pkgName = className.substring(0, lastIndex);
417 }
418 return pkgName;
419 } else {
420 String dPackageName = getDerivedPackageName();
421 if (dPackageName.length() == 0) {
422 return basePackageName;
423 }
424 return basePackageName + '.' + getDerivedPackageName();
425 }
426 }
427
428 protected String getDerivedPackageName() {
429 if (derivedPackageName == null) {
430 int iSep = jspUri.lastIndexOf('/');
431 derivedPackageName = (iSep > 0) ?
432 JspUtil.makeJavaPackage(jspUri.substring(1,iSep)) : "";
433 }
434 return derivedPackageName;
435 }
436
437 /**
438 * The package name into which the servlet class is generated.
439 */
440 public void setServletPackageName(String servletPackageName) {
441 this.basePackageName = servletPackageName;
442 }
443
444 /**
445 * Full path name of the Java file into which the servlet is being
446 * generated.
447 */
448 public String getServletJavaFileName() {
449 if (servletJavaFileName == null) {
450 servletJavaFileName = getOutputDir() + getServletClassName() + ".java";
451 }
452 return servletJavaFileName;
453 }
454
455 /**
456 * Get hold of the Options object for this context.
457 */
458 public Options getOptions() {
459 return options;
460 }
461
462 public ServletContext getServletContext() {
463 return context;
464 }
465
466 public JspRuntimeContext getRuntimeContext() {
467 return rctxt;
468 }
469
470 /**
471 * Path of the Java file relative to the work directory.
472 */
473 public String getJavaPath() {
474
475 if (javaPath != null) {
476 return javaPath;
477 }
478
479 if (isTagFile()) {
480 String tagName = tagInfo.getTagClassName();
481 javaPath = tagName.replace('.', '/') + ".java";
482 } else {
483 javaPath = getServletPackageName().replace('.', '/') + '/' +
484 getServletClassName() + ".java";
485 }
486 return javaPath;
487 }
488
489 public String getClassFileName() {
490 if (classFileName == null) {
491 classFileName = getOutputDir() + getServletClassName() + ".class";
492 }
493 return classFileName;
494 }
495
496 /**
497 * Get the content type of this JSP.
498 *
499 * Content type includes content type and encoding.
500 */
501 public String getContentType() {
502 return contentType;
503 }
504
505 public void setContentType(String contentType) {
506 this.contentType = contentType;
507 }
508
509 /**
510 * Where is the servlet being generated?
511 */
512 public ServletWriter getWriter() {
513 return writer;
514 }
515
516 public void setWriter(ServletWriter writer) {
517 this.writer = writer;
518 }
519
520 /**
521 * Gets the 'location' of the TLD associated with the given taglib 'uri'.
522 *
523 * @return An array of two Strings: The first element denotes the real
524 * path to the TLD. If the path to the TLD points to a jar file, then the
525 * second element denotes the name of the TLD entry in the jar file.
526 * Returns null if the given uri is not associated with any tag library
527 * 'exposed' in the web application.
528 */
529 public String[] getTldLocation(String uri) throws JasperException {
530 String[] location =
531 getOptions().getTldLocationsCache().getLocation(uri);
532 return location;
533 }
534
535 /**
536 * Are we keeping generated code around?
537 */
538 public boolean keepGenerated() {
539 return getOptions().getKeepGenerated();
540 }
541
542 // ==================== Removal ====================
543
544 public void incrementRemoved() {
545 if (removed == 0 && rctxt != null) {
546 rctxt.removeWrapper(jspUri);
547 }
548 removed++;
549 }
550
551 public boolean isRemoved() {
552 if (removed > 1 ) {
553 return true;
554 }
555 return false;
556 }
557
558 // ==================== Compile and reload ====================
559
560 public void compile() throws JasperException, FileNotFoundException {
561 createCompiler();
562 if (isPackagedTagFile || jspCompiler.isOutDated()) {
563 try {
564 jspCompiler.removeGeneratedFiles();
565 jspLoader = null;
566 jspCompiler.compile();
567 jsw.setReload(true);
568 jsw.setCompilationException(null);
569 } catch (JasperException ex) {
570 // Cache compilation exception
571 jsw.setCompilationException(ex);
572 throw ex;
573 } catch (Exception ex) {
574 JasperException je = new JasperException(
575 Localizer.getMessage("jsp.error.unable.compile"),
576 ex);
577 // Cache compilation exception
578 jsw.setCompilationException(je);
579 throw je;
580 }
581 }
582 }
583
584 // ==================== Manipulating the class ====================
585
586 public Class load()
587 throws JasperException, FileNotFoundException
588 {
589 try {
590 getJspLoader();
591
592 String name;
593 if (isTagFile()) {
594 name = tagInfo.getTagClassName();
595 } else {
596 name = getServletPackageName() + "." + getServletClassName();
597 }
598 servletClass = jspLoader.loadClass(name);
599 } catch (ClassNotFoundException cex) {
600 throw new JasperException(Localizer.getMessage("jsp.error.unable.load"),
601 cex);
602 } catch (Exception ex) {
603 throw new JasperException(Localizer.getMessage("jsp.error.unable.compile"),
604 ex);
605 }
606 removed = 0;
607 return servletClass;
608 }
609
610 // ==================== protected methods ====================
611
612 static Object outputDirLock = new Object();
613
614 public void checkOutputDir() {
615 if (outputDir != null) {
616 if (!(new File(outputDir)).exists()) {
617 makeOutputDir();
618 }
619 } else {
620 createOutputDir();
621 }
622 }
623
624 protected boolean makeOutputDir() {
625 synchronized(outputDirLock) {
626 File outDirFile = new File(outputDir);
627 return (outDirFile.exists() || outDirFile.mkdirs());
628 }
629 }
630
631 protected void createOutputDir() {
632 String path = null;
633 if (isTagFile()) {
634 String tagName = tagInfo.getTagClassName();
635 path = tagName.replace('.', '/');
636 path = path.substring(0, path.lastIndexOf('/'));
637 } else {
638 path = getServletPackageName().replace('.', '/');
639 }
640
641 // Append servlet or tag handler path to scratch dir
642 try {
643 baseUrl = options.getScratchDir().toURL();
644 String outUrlString = baseUrl.toString() + '/' + path;
645 URL outUrl = new URL(outUrlString);
646 outputDir = outUrl.getFile() + File.separator;
647 if (!makeOutputDir()) {
648 throw new IllegalStateException(Localizer.getMessage("jsp.error.outputfolder"));
649 }
650 } catch (MalformedURLException e) {
651 throw new IllegalStateException(Localizer.getMessage("jsp.error.outputfolder"), e);
652 }
653 }
654
655 protected static final boolean isPathSeparator(char c) {
656 return (c == '/' || c == '\\');
657 }
658
659 protected static final String canonicalURI(String s) {
660 if (s == null) return null;
661 StringBuffer result = new StringBuffer();
662 final int len = s.length();
663 int pos = 0;
664 while (pos < len) {
665 char c = s.charAt(pos);
666 if ( isPathSeparator(c) ) {
667 /*
668 * multiple path separators.
669 * 'foo///bar' -> 'foo/bar'
670 */
671 while (pos+1 < len && isPathSeparator(s.charAt(pos+1))) {
672 ++pos;
673 }
674
675 if (pos+1 < len && s.charAt(pos+1) == '.') {
676 /*
677 * a single dot at the end of the path - we are done.
678 */
679 if (pos+2 >= len) break;
680
681 switch (s.charAt(pos+2)) {
682 /*
683 * self directory in path
684 * foo/./bar -> foo/bar
685 */
686 case '/':
687 case '\\':
688 pos += 2;
689 continue;
690
691 /*
692 * two dots in a path: go back one hierarchy.
693 * foo/bar/../baz -> foo/baz
694 */
695 case '.':
696 // only if we have exactly _two_ dots.
697 if (pos+3 < len && isPathSeparator(s.charAt(pos+3))) {
698 pos += 3;
699 int separatorPos = result.length()-1;
700 while (separatorPos >= 0 &&
701 ! isPathSeparator(result
702 .charAt(separatorPos))) {
703 --separatorPos;
704 }
705 if (separatorPos >= 0)
706 result.setLength(separatorPos);
707 continue;
708 }
709 }
710 }
711 }
712 result.append(c);
713 ++pos;
714 }
715 return result.toString();
716 }
717 }
718