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