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.compiler;
19
20 import java.io.File;
21 import java.io.FileNotFoundException;
22 import java.io.FileOutputStream;
23 import java.io.OutputStreamWriter;
24 import java.io.PrintWriter;
25 import java.io.UnsupportedEncodingException;
26 import java.net.JarURLConnection;
27 import java.net.URL;
28 import java.net.URLConnection;
29 import java.util.Iterator;
30 import java.util.List;
31
32 import org.apache.jasper.JasperException;
33 import org.apache.jasper.JspCompilationContext;
34 import org.apache.jasper.Options;
35 import org.apache.jasper.servlet.JspServletWrapper;
36
37 /**
38 * Main JSP compiler class. This class uses Ant for compiling.
39 *
40 * @author Anil K. Vijendran
41 * @author Mandar Raje
42 * @author Pierre Delisle
43 * @author Kin-man Chung
44 * @author Remy Maucherat
45 * @author Mark Roth
46 */
47 public abstract class Compiler {
48
49 protected org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
50 .getLog(Compiler.class);
51
52 // ----------------------------------------------------- Instance Variables
53
54 protected JspCompilationContext ctxt;
55
56 protected ErrorDispatcher errDispatcher;
57
58 protected PageInfo pageInfo;
59
60 protected JspServletWrapper jsw;
61
62 protected TagFileProcessor tfp;
63
64 protected Options options;
65
66 protected Node.Nodes pageNodes;
67
68 // ------------------------------------------------------------ Constructor
69
70 public void init(JspCompilationContext ctxt, JspServletWrapper jsw) {
71 this.jsw = jsw;
72 this.ctxt = ctxt;
73 this.options = ctxt.getOptions();
74 }
75
76 // --------------------------------------------------------- Public Methods
77
78 /**
79 * <p>
80 * Retrieves the parsed nodes of the JSP page, if they are available. May
81 * return null. Used in development mode for generating detailed error
82 * messages. http://issues.apache.org/bugzilla/show_bug.cgi?id=37062.
83 * </p>
84 */
85 public Node.Nodes getPageNodes() {
86 return this.pageNodes;
87 }
88
89 /**
90 * Compile the jsp file into equivalent servlet in .java file
91 *
92 * @return a smap for the current JSP page, if one is generated, null
93 * otherwise
94 */
95 protected String[] generateJava() throws Exception {
96
97 String[] smapStr = null;
98
99 long t1, t2, t3, t4;
100
101 t1 = t2 = t3 = t4 = 0;
102
103 if (log.isDebugEnabled()) {
104 t1 = System.currentTimeMillis();
105 }
106
107 // Setup page info area
108 pageInfo = new PageInfo(new BeanRepository(ctxt.getClassLoader(),
109 errDispatcher), ctxt.getJspFile());
110
111 JspConfig jspConfig = options.getJspConfig();
112 JspConfig.JspProperty jspProperty = jspConfig.findJspProperty(ctxt
113 .getJspFile());
114
115 /*
116 * If the current uri is matched by a pattern specified in a
117 * jsp-property-group in web.xml, initialize pageInfo with those
118 * properties.
119 */
120 if (jspProperty.isELIgnored() != null) {
121 pageInfo.setELIgnored(JspUtil.booleanValue(jspProperty
122 .isELIgnored()));
123 }
124 if (jspProperty.isScriptingInvalid() != null) {
125 pageInfo.setScriptingInvalid(JspUtil.booleanValue(jspProperty
126 .isScriptingInvalid()));
127 }
128 if (jspProperty.getIncludePrelude() != null) {
129 pageInfo.setIncludePrelude(jspProperty.getIncludePrelude());
130 }
131 if (jspProperty.getIncludeCoda() != null) {
132 pageInfo.setIncludeCoda(jspProperty.getIncludeCoda());
133 }
134 if (jspProperty.isDeferedSyntaxAllowedAsLiteral() != null) {
135 pageInfo.setDeferredSyntaxAllowedAsLiteral(JspUtil.booleanValue(jspProperty
136 .isDeferedSyntaxAllowedAsLiteral()));
137 }
138 if (jspProperty.isTrimDirectiveWhitespaces() != null) {
139 pageInfo.setTrimDirectiveWhitespaces(JspUtil.booleanValue(jspProperty
140 .isTrimDirectiveWhitespaces()));
141 }
142 if (ctxt.isTagFile()) {
143 try {
144 double libraryVersion = Double.parseDouble(ctxt.getTagInfo()
145 .getTagLibrary().getRequiredVersion());
146 if (libraryVersion < 2.0) {
147 pageInfo.setIsELIgnored("true", null, errDispatcher, true);
148 }
149 if (libraryVersion < 2.1) {
150 pageInfo.setDeferredSyntaxAllowedAsLiteral("true", null,
151 errDispatcher, true);
152 }
153 } catch (NumberFormatException ex) {
154 errDispatcher.jspError(ex);
155 }
156 }
157
158 ctxt.checkOutputDir();
159 String javaFileName = ctxt.getServletJavaFileName();
160
161 ServletWriter writer = null;
162 try {
163 /*
164 * The setting of isELIgnored changes the behaviour of the parser
165 * in subtle ways. To add to the 'fun', isELIgnored can be set in
166 * any file that forms part of the translation unit so setting it
167 * in a file included towards the end of the translation unit can
168 * change how the parser should have behaved when parsing content
169 * up to the point where isELIgnored was set. Arghh!
170 * Previous attempts to hack around this have only provided partial
171 * solutions. We now use two passes to parse the translation unit.
172 * The first just parses the directives and the second parses the
173 * whole translation unit once we know how isELIgnored has been set.
174 * TODO There are some possible optimisations of this process.
175 */
176 // Parse the file
177 ParserController parserCtl = new ParserController(ctxt, this);
178
179 // Pass 1 - the directives
180 Node.Nodes directives =
181 parserCtl.parseDirectives(ctxt.getJspFile());
182 Validator.validateDirectives(this, directives);
183
184 // Pass 2 - the whole translation unit
185 pageNodes = parserCtl.parse(ctxt.getJspFile());
186
187 if (ctxt.isPrototypeMode()) {
188 // generate prototype .java file for the tag file
189 writer = setupContextWriter(javaFileName);
190 Generator.generate(writer, this, pageNodes);
191 writer.close();
192 writer = null;
193 return null;
194 }
195
196 // Validate and process attributes - don't re-validate the
197 // directives we validated in pass 1
198 Validator.validateExDirectives(this, pageNodes);
199
200 if (log.isDebugEnabled()) {
201 t2 = System.currentTimeMillis();
202 }
203
204 // Collect page info
205 Collector.collect(this, pageNodes);
206
207 // Compile (if necessary) and load the tag files referenced in
208 // this compilation unit.
209 tfp = new TagFileProcessor();
210 tfp.loadTagFiles(this, pageNodes);
211
212 if (log.isDebugEnabled()) {
213 t3 = System.currentTimeMillis();
214 }
215
216 // Determine which custom tag needs to declare which scripting vars
217 ScriptingVariabler.set(pageNodes, errDispatcher);
218
219 // Optimizations by Tag Plugins
220 TagPluginManager tagPluginManager = options.getTagPluginManager();
221 tagPluginManager.apply(pageNodes, errDispatcher, pageInfo);
222
223 // Optimization: concatenate contiguous template texts.
224 TextOptimizer.concatenate(this, pageNodes);
225
226 // Generate static function mapper codes.
227 ELFunctionMapper.map(this, pageNodes);
228
229 // generate servlet .java file
230 writer = setupContextWriter(javaFileName);
231 Generator.generate(writer, this, pageNodes);
232 writer.close();
233 writer = null;
234
235 // The writer is only used during the compile, dereference
236 // it in the JspCompilationContext when done to allow it
237 // to be GC'd and save memory.
238 ctxt.setWriter(null);
239
240 if (log.isDebugEnabled()) {
241 t4 = System.currentTimeMillis();
242 log.debug("Generated " + javaFileName + " total=" + (t4 - t1)
243 + " generate=" + (t4 - t3) + " validate=" + (t2 - t1));
244 }
245
246 } catch (Exception e) {
247 if (writer != null) {
248 try {
249 writer.close();
250 writer = null;
251 } catch (Exception e1) {
252 // do nothing
253 }
254 }
255 // Remove the generated .java file
256 new File(javaFileName).delete();
257 throw e;
258 } finally {
259 if (writer != null) {
260 try {
261 writer.close();
262 } catch (Exception e2) {
263 // do nothing
264 }
265 }
266 }
267
268 // JSR45 Support
269 if (!options.isSmapSuppressed()) {
270 smapStr = SmapUtil.generateSmap(ctxt, pageNodes);
271 }
272
273 // If any proto type .java and .class files was generated,
274 // the prototype .java may have been replaced by the current
275 // compilation (if the tag file is self referencing), but the
276 // .class file need to be removed, to make sure that javac would
277 // generate .class again from the new .java file just generated.
278 tfp.removeProtoTypeFiles(ctxt.getClassFileName());
279
280 return smapStr;
281 }
282
283 private ServletWriter setupContextWriter(String javaFileName)
284 throws FileNotFoundException, JasperException {
285 ServletWriter writer;
286 // Setup the ServletWriter
287 String javaEncoding = ctxt.getOptions().getJavaEncoding();
288 OutputStreamWriter osw = null;
289
290 try {
291 osw = new OutputStreamWriter(
292 new FileOutputStream(javaFileName), javaEncoding);
293 } catch (UnsupportedEncodingException ex) {
294 errDispatcher.jspError("jsp.error.needAlternateJavaEncoding",
295 javaEncoding);
296 }
297
298 writer = new ServletWriter(new PrintWriter(osw));
299 ctxt.setWriter(writer);
300 return writer;
301 }
302
303 /**
304 * Compile the servlet from .java file to .class file
305 */
306 protected abstract void generateClass(String[] smap)
307 throws FileNotFoundException, JasperException, Exception;
308
309 /**
310 * Compile the jsp file from the current engine context
311 */
312 public void compile() throws FileNotFoundException, JasperException,
313 Exception {
314 compile(true);
315 }
316
317 /**
318 * Compile the jsp file from the current engine context. As an side- effect,
319 * tag files that are referenced by this page are also compiled.
320 *
321 * @param compileClass
322 * If true, generate both .java and .class file If false,
323 * generate only .java file
324 */
325 public void compile(boolean compileClass) throws FileNotFoundException,
326 JasperException, Exception {
327 compile(compileClass, false);
328 }
329
330 /**
331 * Compile the jsp file from the current engine context. As an side- effect,
332 * tag files that are referenced by this page are also compiled.
333 *
334 * @param compileClass
335 * If true, generate both .java and .class file If false,
336 * generate only .java file
337 * @param jspcMode
338 * true if invoked from JspC, false otherwise
339 */
340 public void compile(boolean compileClass, boolean jspcMode)
341 throws FileNotFoundException, JasperException, Exception {
342 if (errDispatcher == null) {
343 this.errDispatcher = new ErrorDispatcher(jspcMode);
344 }
345
346 try {
347 String[] smap = generateJava();
348 if (compileClass) {
349 generateClass(smap);
350 // Fix for bugzilla 41606
351 // Set JspServletWrapper.servletClassLastModifiedTime after successful compile
352 String targetFileName = ctxt.getClassFileName();
353 if (targetFileName != null) {
354 File targetFile = new File(targetFileName);
355 if (targetFile.exists() && jsw != null) {
356 jsw.setServletClassLastModifiedTime(targetFile.lastModified());
357 }
358 }
359 }
360 } finally {
361 if (tfp != null && ctxt.isPrototypeMode()) {
362 tfp.removeProtoTypeFiles(null);
363 }
364 // Make sure these object which are only used during the
365 // generation and compilation of the JSP page get
366 // dereferenced so that they can be GC'd and reduce the
367 // memory footprint.
368 tfp = null;
369 errDispatcher = null;
370 pageInfo = null;
371
372 // Only get rid of the pageNodes if in production.
373 // In development mode, they are used for detailed
374 // error messages.
375 // http://issues.apache.org/bugzilla/show_bug.cgi?id=37062
376 if (!this.options.getDevelopment()) {
377 pageNodes = null;
378 }
379
380 if (ctxt.getWriter() != null) {
381 ctxt.getWriter().close();
382 ctxt.setWriter(null);
383 }
384 }
385 }
386
387 /**
388 * This is a protected method intended to be overridden by subclasses of
389 * Compiler. This is used by the compile method to do all the compilation.
390 */
391 public boolean isOutDated() {
392 return isOutDated(true);
393 }
394
395 /**
396 * Determine if a compilation is necessary by checking the time stamp of the
397 * JSP page with that of the corresponding .class or .java file. If the page
398 * has dependencies, the check is also extended to its dependeants, and so
399 * on. This method can by overidden by a subclasses of Compiler.
400 *
401 * @param checkClass
402 * If true, check against .class file, if false, check against
403 * .java file.
404 */
405 public boolean isOutDated(boolean checkClass) {
406
407 String jsp = ctxt.getJspFile();
408
409 if (jsw != null
410 && (ctxt.getOptions().getModificationTestInterval() > 0)) {
411
412 if (jsw.getLastModificationTest()
413 + (ctxt.getOptions().getModificationTestInterval() * 1000) > System
414 .currentTimeMillis()) {
415 return false;
416 } else {
417 jsw.setLastModificationTest(System.currentTimeMillis());
418 }
419 }
420
421 long jspRealLastModified = 0;
422 try {
423 URL jspUrl = ctxt.getResource(jsp);
424 if (jspUrl == null) {
425 ctxt.incrementRemoved();
426 return true;
427 }
428 URLConnection uc = jspUrl.openConnection();
429 if (uc instanceof JarURLConnection) {
430 jspRealLastModified =
431 ((JarURLConnection) uc).getJarEntry().getTime();
432 } else {
433 jspRealLastModified = uc.getLastModified();
434 }
435 uc.getInputStream().close();
436 } catch (Exception e) {
437 log.debug("Problem accessing resource. Treat as outdated.", e);
438 return true;
439 }
440
441 long targetLastModified = 0;
442 File targetFile;
443
444 if (checkClass) {
445 targetFile = new File(ctxt.getClassFileName());
446 } else {
447 targetFile = new File(ctxt.getServletJavaFileName());
448 }
449
450 if (!targetFile.exists()) {
451 return true;
452 }
453
454 targetLastModified = targetFile.lastModified();
455 if (checkClass && jsw != null) {
456 jsw.setServletClassLastModifiedTime(targetLastModified);
457 }
458 if (targetLastModified < jspRealLastModified) {
459 if (log.isDebugEnabled()) {
460 log.debug("Compiler: outdated: " + targetFile + " "
461 + targetLastModified);
462 }
463 return true;
464 }
465
466 // determine if source dependent files (e.g. includes using include
467 // directives) have been changed.
468 if (jsw == null) {
469 return false;
470 }
471
472 List depends = jsw.getDependants();
473 if (depends == null) {
474 return false;
475 }
476
477 Iterator it = depends.iterator();
478 while (it.hasNext()) {
479 String include = (String) it.next();
480 try {
481 URL includeUrl = ctxt.getResource(include);
482 if (includeUrl == null) {
483 return true;
484 }
485
486 URLConnection iuc = includeUrl.openConnection();
487 long includeLastModified = 0;
488 if (iuc instanceof JarURLConnection) {
489 includeLastModified =
490 ((JarURLConnection) iuc).getJarEntry().getTime();
491 } else {
492 includeLastModified = iuc.getLastModified();
493 }
494 iuc.getInputStream().close();
495
496 if (includeLastModified > targetLastModified) {
497 return true;
498 }
499 } catch (Exception e) {
500 log.debug("Problem accessing resource. Treat as outdated.", e);
501 return true;
502 }
503 }
504
505 return false;
506
507 }
508
509 /**
510 * Gets the error dispatcher.
511 */
512 public ErrorDispatcher getErrorDispatcher() {
513 return errDispatcher;
514 }
515
516 /**
517 * Gets the info about the page under compilation
518 */
519 public PageInfo getPageInfo() {
520 return pageInfo;
521 }
522
523 public JspCompilationContext getCompilationContext() {
524 return ctxt;
525 }
526
527 /**
528 * Remove generated files
529 */
530 public void removeGeneratedFiles() {
531 try {
532 String classFileName = ctxt.getClassFileName();
533 if (classFileName != null) {
534 File classFile = new File(classFileName);
535 if (log.isDebugEnabled())
536 log.debug("Deleting " + classFile);
537 classFile.delete();
538 }
539 } catch (Exception e) {
540 // Remove as much as possible, ignore possible exceptions
541 }
542 try {
543 String javaFileName = ctxt.getServletJavaFileName();
544 if (javaFileName != null) {
545 File javaFile = new File(javaFileName);
546 if (log.isDebugEnabled())
547 log.debug("Deleting " + javaFile);
548 javaFile.delete();
549 }
550 } catch (Exception e) {
551 // Remove as much as possible, ignore possible exceptions
552 }
553 }
554
555 public void removeGeneratedClassFiles() {
556 try {
557 String classFileName = ctxt.getClassFileName();
558 if (classFileName != null) {
559 File classFile = new File(classFileName);
560 if (log.isDebugEnabled())
561 log.debug("Deleting " + classFile);
562 classFile.delete();
563 }
564 } catch (Exception e) {
565 // Remove as much as possible, ignore possible exceptions
566 }
567 }
568 }