001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.jasper.compiler;
019:
020: import java.io.File;
021: import java.io.FileNotFoundException;
022: import java.io.FileOutputStream;
023: import java.io.OutputStreamWriter;
024: import java.io.PrintWriter;
025: import java.io.UnsupportedEncodingException;
026: import java.net.URL;
027: import java.net.URLConnection;
028: import java.util.Iterator;
029: import java.util.List;
030:
031: import org.apache.jasper.JasperException;
032: import org.apache.jasper.JspCompilationContext;
033: import org.apache.jasper.Options;
034: import org.apache.jasper.servlet.JspServletWrapper;
035:
036: /**
037: * Main JSP compiler class. This class uses Ant for compiling.
038: *
039: * @author Anil K. Vijendran
040: * @author Mandar Raje
041: * @author Pierre Delisle
042: * @author Kin-man Chung
043: * @author Remy Maucherat
044: * @author Mark Roth
045: */
046: public abstract class Compiler {
047:
048: protected org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
049: .getLog(Compiler.class);
050:
051: // ----------------------------------------------------- Instance Variables
052:
053: protected JspCompilationContext ctxt;
054:
055: protected ErrorDispatcher errDispatcher;
056:
057: protected PageInfo pageInfo;
058:
059: protected JspServletWrapper jsw;
060:
061: protected TagFileProcessor tfp;
062:
063: protected Options options;
064:
065: protected Node.Nodes pageNodes;
066:
067: // ------------------------------------------------------------ Constructor
068:
069: public void init(JspCompilationContext ctxt, JspServletWrapper jsw) {
070: this .jsw = jsw;
071: this .ctxt = ctxt;
072: this .options = ctxt.getOptions();
073: }
074:
075: // --------------------------------------------------------- Public Methods
076:
077: /**
078: * <p>
079: * Retrieves the parsed nodes of the JSP page, if they are available. May
080: * return null. Used in development mode for generating detailed error
081: * messages. http://issues.apache.org/bugzilla/show_bug.cgi?id=37062.
082: * </p>
083: */
084: public Node.Nodes getPageNodes() {
085: return this .pageNodes;
086: }
087:
088: /**
089: * Compile the jsp file into equivalent servlet in .java file
090: *
091: * @return a smap for the current JSP page, if one is generated, null
092: * otherwise
093: */
094: protected String[] generateJava() throws Exception {
095:
096: String[] smapStr = null;
097:
098: long t1, t2, t3, t4;
099:
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
108: .getClassLoader(), errDispatcher), ctxt.getJspFile());
109:
110: JspConfig jspConfig = options.getJspConfig();
111: JspConfig.JspProperty jspProperty = jspConfig
112: .findJspProperty(ctxt.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
125: .booleanValue(jspProperty.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
135: .booleanValue(jspProperty
136: .isDeferedSyntaxAllowedAsLiteral()));
137: }
138: if (jspProperty.isTrimDirectiveWhitespaces() != null) {
139: pageInfo.setTrimDirectiveWhitespaces(JspUtil
140: .booleanValue(jspProperty
141: .isTrimDirectiveWhitespaces()));
142: }
143:
144: ctxt.checkOutputDir();
145: String javaFileName = ctxt.getServletJavaFileName();
146: ServletWriter writer = null;
147:
148: try {
149: // Setup the ServletWriter
150: String javaEncoding = ctxt.getOptions().getJavaEncoding();
151: OutputStreamWriter osw = null;
152:
153: try {
154: osw = new OutputStreamWriter(new FileOutputStream(
155: javaFileName), javaEncoding);
156: } catch (UnsupportedEncodingException ex) {
157: errDispatcher.jspError(
158: "jsp.error.needAlternateJavaEncoding",
159: javaEncoding);
160: }
161:
162: writer = new ServletWriter(new PrintWriter(osw));
163: ctxt.setWriter(writer);
164:
165: // Reset the temporary variable counter for the generator.
166: JspUtil.resetTemporaryVariableName();
167:
168: // Parse the file
169: ParserController parserCtl = new ParserController(ctxt,
170: this );
171: pageNodes = parserCtl.parse(ctxt.getJspFile());
172:
173: if (ctxt.isPrototypeMode()) {
174: // generate prototype .java file for the tag file
175: Generator.generate(writer, this , pageNodes);
176: writer.close();
177: writer = null;
178: return null;
179: }
180:
181: // Validate and process attributes
182: Validator.validate(this , pageNodes);
183:
184: if (log.isDebugEnabled()) {
185: t2 = System.currentTimeMillis();
186: }
187:
188: // Collect page info
189: Collector.collect(this , pageNodes);
190:
191: // Compile (if necessary) and load the tag files referenced in
192: // this compilation unit.
193: tfp = new TagFileProcessor();
194: tfp.loadTagFiles(this , pageNodes);
195:
196: if (log.isDebugEnabled()) {
197: t3 = System.currentTimeMillis();
198: }
199:
200: // Determine which custom tag needs to declare which scripting vars
201: ScriptingVariabler.set(pageNodes, errDispatcher);
202:
203: // Optimizations by Tag Plugins
204: TagPluginManager tagPluginManager = options
205: .getTagPluginManager();
206: tagPluginManager.apply(pageNodes, errDispatcher, pageInfo);
207:
208: // Optimization: concatenate contiguous template texts.
209: TextOptimizer.concatenate(this , pageNodes);
210:
211: // Generate static function mapper codes.
212: ELFunctionMapper.map(this , pageNodes);
213:
214: // generate servlet .java file
215: Generator.generate(writer, this , pageNodes);
216: writer.close();
217: writer = null;
218:
219: // The writer is only used during the compile, dereference
220: // it in the JspCompilationContext when done to allow it
221: // to be GC'd and save memory.
222: ctxt.setWriter(null);
223:
224: if (log.isDebugEnabled()) {
225: t4 = System.currentTimeMillis();
226: log.debug("Generated " + javaFileName + " total="
227: + (t4 - t1) + " generate=" + (t4 - t3)
228: + " validate=" + (t2 - t1));
229: }
230:
231: } catch (Exception e) {
232: if (writer != null) {
233: try {
234: writer.close();
235: writer = null;
236: } catch (Exception e1) {
237: // do nothing
238: }
239: }
240: // Remove the generated .java file
241: new File(javaFileName).delete();
242: throw e;
243: } finally {
244: if (writer != null) {
245: try {
246: writer.close();
247: } catch (Exception e2) {
248: // do nothing
249: }
250: }
251: }
252:
253: // JSR45 Support
254: if (!options.isSmapSuppressed()) {
255: smapStr = SmapUtil.generateSmap(ctxt, pageNodes);
256: }
257:
258: // If any proto type .java and .class files was generated,
259: // the prototype .java may have been replaced by the current
260: // compilation (if the tag file is self referencing), but the
261: // .class file need to be removed, to make sure that javac would
262: // generate .class again from the new .java file just generated.
263: tfp.removeProtoTypeFiles(ctxt.getClassFileName());
264:
265: return smapStr;
266: }
267:
268: /**
269: * Compile the servlet from .java file to .class file
270: */
271: protected abstract void generateClass(String[] smap)
272: throws FileNotFoundException, JasperException, Exception;
273:
274: /**
275: * Compile the jsp file from the current engine context
276: */
277: public void compile() throws FileNotFoundException,
278: JasperException, Exception {
279: compile(true);
280: }
281:
282: /**
283: * Compile the jsp file from the current engine context. As an side- effect,
284: * tag files that are referenced by this page are also compiled.
285: *
286: * @param compileClass
287: * If true, generate both .java and .class file If false,
288: * generate only .java file
289: */
290: public void compile(boolean compileClass)
291: throws FileNotFoundException, JasperException, Exception {
292: compile(compileClass, false);
293: }
294:
295: /**
296: * Compile the jsp file from the current engine context. As an side- effect,
297: * tag files that are referenced by this page are also compiled.
298: *
299: * @param compileClass
300: * If true, generate both .java and .class file If false,
301: * generate only .java file
302: * @param jspcMode
303: * true if invoked from JspC, false otherwise
304: */
305: public void compile(boolean compileClass, boolean jspcMode)
306: throws FileNotFoundException, JasperException, Exception {
307: if (errDispatcher == null) {
308: this .errDispatcher = new ErrorDispatcher(jspcMode);
309: }
310:
311: try {
312: String[] smap = generateJava();
313: if (compileClass) {
314: generateClass(smap);
315: }
316: } finally {
317: if (tfp != null) {
318: tfp.removeProtoTypeFiles(null);
319: }
320: // Make sure these object which are only used during the
321: // generation and compilation of the JSP page get
322: // dereferenced so that they can be GC'd and reduce the
323: // memory footprint.
324: tfp = null;
325: errDispatcher = null;
326: pageInfo = null;
327:
328: // Only get rid of the pageNodes if in production.
329: // In development mode, they are used for detailed
330: // error messages.
331: // http://issues.apache.org/bugzilla/show_bug.cgi?id=37062
332: if (!this .options.getDevelopment()) {
333: pageNodes = null;
334: }
335:
336: if (ctxt.getWriter() != null) {
337: ctxt.getWriter().close();
338: ctxt.setWriter(null);
339: }
340: }
341: }
342:
343: /**
344: * This is a protected method intended to be overridden by subclasses of
345: * Compiler. This is used by the compile method to do all the compilation.
346: */
347: public boolean isOutDated() {
348: return isOutDated(true);
349: }
350:
351: /**
352: * Determine if a compilation is necessary by checking the time stamp of the
353: * JSP page with that of the corresponding .class or .java file. If the page
354: * has dependencies, the check is also extended to its dependeants, and so
355: * on. This method can by overidden by a subclasses of Compiler.
356: *
357: * @param checkClass
358: * If true, check against .class file, if false, check against
359: * .java file.
360: */
361: public boolean isOutDated(boolean checkClass) {
362:
363: String jsp = ctxt.getJspFile();
364:
365: if (jsw != null
366: && (ctxt.getOptions().getModificationTestInterval() > 0)) {
367:
368: if (jsw.getLastModificationTest()
369: + (ctxt.getOptions().getModificationTestInterval() * 1000) > System
370: .currentTimeMillis()) {
371: return false;
372: } else {
373: jsw.setLastModificationTest(System.currentTimeMillis());
374: }
375: }
376:
377: long jspRealLastModified = 0;
378: try {
379: URL jspUrl = ctxt.getResource(jsp);
380: if (jspUrl == null) {
381: ctxt.incrementRemoved();
382: return false;
383: }
384: URLConnection uc = jspUrl.openConnection();
385: jspRealLastModified = uc.getLastModified();
386: uc.getInputStream().close();
387: } catch (Exception e) {
388: return true;
389: }
390:
391: long targetLastModified = 0;
392: File targetFile;
393:
394: if (checkClass) {
395: targetFile = new File(ctxt.getClassFileName());
396: } else {
397: targetFile = new File(ctxt.getServletJavaFileName());
398: }
399:
400: if (!targetFile.exists()) {
401: return true;
402: }
403:
404: targetLastModified = targetFile.lastModified();
405: if (checkClass && jsw != null) {
406: jsw.setServletClassLastModifiedTime(targetLastModified);
407: }
408: if (targetLastModified < jspRealLastModified) {
409: if (log.isDebugEnabled()) {
410: log.debug("Compiler: outdated: " + targetFile + " "
411: + targetLastModified);
412: }
413: return true;
414: }
415:
416: // determine if source dependent files (e.g. includes using include
417: // directives) have been changed.
418: if (jsw == null) {
419: return false;
420: }
421:
422: List depends = jsw.getDependants();
423: if (depends == null) {
424: return false;
425: }
426:
427: Iterator it = depends.iterator();
428: while (it.hasNext()) {
429: String include = (String) it.next();
430: try {
431: URL includeUrl = ctxt.getResource(include);
432: if (includeUrl == null) {
433: return true;
434: }
435:
436: URLConnection includeUconn = includeUrl
437: .openConnection();
438: long includeLastModified = includeUconn
439: .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: }
|