001: /*
002: * Copyright 1999,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.jasper.compiler;
018:
019: import java.io.File;
020: import java.io.FileNotFoundException;
021: import java.io.FileOutputStream;
022: import java.io.OutputStreamWriter;
023: import java.io.PrintStream;
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: import java.util.StringTokenizer;
031:
032: import org.apache.jasper.JasperException;
033: import org.apache.jasper.JspCompilationContext;
034: import org.apache.jasper.Options;
035: import org.apache.jasper.servlet.JspServletWrapper;
036: import org.apache.jasper.util.SystemLogHandler;
037: import org.apache.tools.ant.BuildException;
038: import org.apache.tools.ant.DefaultLogger;
039: import org.apache.tools.ant.Project;
040: import org.apache.tools.ant.taskdefs.Javac;
041: import org.apache.tools.ant.types.Path;
042: import org.apache.tools.ant.types.PatternSet;
043:
044: /**
045: * Main JSP compiler class. This class uses Ant for compiling.
046: *
047: * @author Anil K. Vijendran
048: * @author Mandar Raje
049: * @author Pierre Delisle
050: * @author Kin-man Chung
051: * @author Remy Maucherat
052: * @author Mark Roth
053: */
054: public class Compiler {
055: private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
056: .getLog(Compiler.class);
057:
058: // ----------------------------------------------------------------- Static
059:
060: // Some javac are not thread safe; use a lock to serialize compilation,
061: static Object javacLock = new Object();
062:
063: // ----------------------------------------------------- Instance Variables
064:
065: protected JspCompilationContext ctxt;
066:
067: private ErrorDispatcher errDispatcher;
068: private PageInfo pageInfo;
069: private JspServletWrapper jsw;
070: private JasperAntLogger logger;
071: private TagFileProcessor tfp;
072:
073: protected Project project = null;
074:
075: protected Options options;
076:
077: protected Node.Nodes pageNodes;
078:
079: // ------------------------------------------------------------ Constructor
080:
081: public Compiler(JspCompilationContext ctxt) {
082: this (ctxt, null);
083: }
084:
085: public Compiler(JspCompilationContext ctxt, JspServletWrapper jsw) {
086: this .jsw = jsw;
087: this .ctxt = ctxt;
088: this .options = ctxt.getOptions();
089: }
090:
091: // Lazy eval - if we don't need to compile we probably don't need the project
092: private Project getProject() {
093:
094: if (project != null)
095: return project;
096:
097: // Initializing project
098: project = new Project();
099: logger = new JasperAntLogger();
100: logger.setOutputPrintStream(System.out);
101: logger.setErrorPrintStream(System.err);
102: logger.setMessageOutputLevel(Project.MSG_INFO);
103: project.addBuildListener(logger);
104: if (System.getProperty("catalina.home") != null) {
105: project.setBasedir(System.getProperty("catalina.home"));
106: }
107:
108: if (options.getCompiler() != null) {
109: if (log.isDebugEnabled())
110: log.debug("Compiler " + options.getCompiler());
111: project
112: .setProperty("build.compiler", options
113: .getCompiler());
114: }
115: project.init();
116: return project;
117: }
118:
119: class JasperAntLogger extends DefaultLogger {
120:
121: private StringBuffer reportBuf = new StringBuffer();
122:
123: protected void printMessage(final String message,
124: final PrintStream stream, final int priority) {
125: }
126:
127: protected void log(String message) {
128: reportBuf.append(message);
129: reportBuf.append(System.getProperty("line.separator"));
130: }
131:
132: protected String getReport() {
133: String report = reportBuf.toString();
134: reportBuf.setLength(0);
135: return report;
136: }
137: }
138:
139: // --------------------------------------------------------- Public Methods
140:
141: /**
142: * Compile the jsp file into equivalent servlet in .java file
143: * @return a smap for the current JSP page, if one is generated,
144: * null otherwise
145: */
146: private String[] generateJava() throws Exception {
147:
148: String[] smapStr = null;
149:
150: long t1 = System.currentTimeMillis();
151:
152: // Setup page info area
153: pageInfo = new PageInfo(new BeanRepository(ctxt
154: .getClassLoader(), errDispatcher));
155:
156: JspConfig jspConfig = options.getJspConfig();
157: JspConfig.JspProperty jspProperty = jspConfig
158: .findJspProperty(ctxt.getJspFile());
159:
160: /*
161: * If the current uri is matched by a pattern specified in
162: * a jsp-property-group in web.xml, initialize pageInfo with
163: * those properties.
164: */
165: pageInfo.setELIgnored(JspUtil.booleanValue(jspProperty
166: .isELIgnored()));
167: pageInfo.setScriptingInvalid(JspUtil.booleanValue(jspProperty
168: .isScriptingInvalid()));
169: if (jspProperty.getIncludePrelude() != null) {
170: pageInfo.setIncludePrelude(jspProperty.getIncludePrelude());
171: }
172: if (jspProperty.getIncludeCoda() != null) {
173: pageInfo.setIncludeCoda(jspProperty.getIncludeCoda());
174: }
175:
176: String javaFileName = ctxt.getServletJavaFileName();
177: ServletWriter writer = null;
178:
179: try {
180: // Setup the ServletWriter
181: String javaEncoding = ctxt.getOptions().getJavaEncoding();
182: OutputStreamWriter osw = null;
183:
184: try {
185: osw = new OutputStreamWriter(new FileOutputStream(
186: javaFileName), javaEncoding);
187: } catch (UnsupportedEncodingException ex) {
188: errDispatcher.jspError(
189: "jsp.error.needAlternateJavaEncoding",
190: javaEncoding);
191: }
192:
193: writer = new ServletWriter(new PrintWriter(osw));
194: ctxt.setWriter(writer);
195:
196: // Reset the temporary variable counter for the generator.
197: JspUtil.resetTemporaryVariableName();
198:
199: // Parse the file
200: ParserController parserCtl = new ParserController(ctxt,
201: this );
202: pageNodes = parserCtl.parse(ctxt.getJspFile());
203:
204: if (ctxt.isPrototypeMode()) {
205: // generate prototype .java file for the tag file
206: Generator.generate(writer, this , pageNodes);
207: writer.close();
208: writer = null;
209: return null;
210: }
211:
212: // Validate and process attributes
213: Validator.validate(this , pageNodes);
214:
215: long t2 = System.currentTimeMillis();
216: // Dump out the page (for debugging)
217: // Dumper.dump(pageNodes);
218:
219: // Collect page info
220: Collector.collect(this , pageNodes);
221:
222: // Compile (if necessary) and load the tag files referenced in
223: // this compilation unit.
224: tfp = new TagFileProcessor();
225: tfp.loadTagFiles(this , pageNodes);
226:
227: long t3 = System.currentTimeMillis();
228:
229: // Determine which custom tag needs to declare which scripting vars
230: ScriptingVariabler.set(pageNodes, errDispatcher);
231:
232: // Optimizations by Tag Plugins
233: TagPluginManager tagPluginManager = options
234: .getTagPluginManager();
235: tagPluginManager.apply(pageNodes, errDispatcher, pageInfo);
236:
237: // Optimization: concatenate contiguous template texts.
238: TextOptimizer.concatenate(this , pageNodes);
239:
240: // Generate static function mapper codes.
241: ELFunctionMapper.map(this , pageNodes);
242:
243: // generate servlet .java file
244: Generator.generate(writer, this , pageNodes);
245: writer.close();
246: writer = null;
247:
248: // The writer is only used during the compile, dereference
249: // it in the JspCompilationContext when done to allow it
250: // to be GC'd and save memory.
251: ctxt.setWriter(null);
252:
253: long t4 = System.currentTimeMillis();
254: if (t4 - t1 > 500) {
255: log.debug("Generated " + javaFileName + " total="
256: + (t4 - t1) + " generate=" + (t4 - t3)
257: + " validate=" + (t2 - t1));
258: }
259:
260: } catch (Exception e) {
261: if (writer != null) {
262: try {
263: writer.close();
264: writer = null;
265: } catch (Exception e1) {
266: // do nothing
267: }
268: }
269: // Remove the generated .java file
270: new File(javaFileName).delete();
271: throw e;
272: } finally {
273: if (writer != null) {
274: try {
275: writer.close();
276: } catch (Exception e2) {
277: // do nothing
278: }
279: }
280: }
281:
282: // JSR45 Support
283: if (!options.isSmapSuppressed()) {
284: smapStr = SmapUtil.generateSmap(ctxt, pageNodes);
285: }
286:
287: // If any proto type .java and .class files was generated,
288: // the prototype .java may have been replaced by the current
289: // compilation (if the tag file is self referencing), but the
290: // .class file need to be removed, to make sure that javac would
291: // generate .class again from the new .java file just generated.
292: tfp.removeProtoTypeFiles(ctxt.getClassFileName());
293:
294: return smapStr;
295: }
296:
297: /**
298: * Compile the servlet from .java file to .class file
299: */
300: private void generateClass(String[] smap)
301: throws FileNotFoundException, JasperException, Exception {
302:
303: long t1 = System.currentTimeMillis();
304: String javaEncoding = ctxt.getOptions().getJavaEncoding();
305: String javaFileName = ctxt.getServletJavaFileName();
306: String classpath = ctxt.getClassPath();
307:
308: String sep = System.getProperty("path.separator");
309:
310: StringBuffer errorReport = new StringBuffer();
311:
312: StringBuffer info = new StringBuffer();
313: info.append("Compile: javaFileName=" + javaFileName + "\n");
314: info.append(" classpath=" + classpath + "\n");
315:
316: // Start capturing the System.err output for this thread
317: SystemLogHandler.setThread();
318:
319: // Initializing javac task
320: getProject();
321: Javac javac = (Javac) project.createTask("javac");
322:
323: // Initializing classpath
324: Path path = new Path(project);
325: path.setPath(System.getProperty("java.class.path"));
326: info.append(" cp=" + System.getProperty("java.class.path")
327: + "\n");
328: StringTokenizer tokenizer = new StringTokenizer(classpath, sep);
329: while (tokenizer.hasMoreElements()) {
330: String pathElement = tokenizer.nextToken();
331: File repository = new File(pathElement);
332: path.setLocation(repository);
333: info.append(" cp=" + repository + "\n");
334: }
335:
336: if (log.isDebugEnabled())
337: log.debug("Using classpath: "
338: + System.getProperty("java.class.path") + sep
339: + classpath);
340:
341: // Initializing sourcepath
342: Path srcPath = new Path(project);
343: srcPath.setLocation(options.getScratchDir());
344:
345: info.append(" work dir=" + options.getScratchDir() + "\n");
346:
347: // Initialize and set java extensions
348: String exts = System.getProperty("java.ext.dirs");
349: if (exts != null) {
350: Path extdirs = new Path(project);
351: extdirs.setPath(exts);
352: javac.setExtdirs(extdirs);
353: info.append(" extension dir=" + exts + "\n");
354: }
355:
356: // Configure the compiler object
357: javac.setEncoding(javaEncoding);
358: javac.setClasspath(path);
359: javac.setDebug(ctxt.getOptions().getClassDebugInfo());
360: javac.setSrcdir(srcPath);
361: javac.setOptimize(!ctxt.getOptions().getClassDebugInfo());
362: javac.setFork(ctxt.getOptions().getFork());
363: info.append(" srcDir=" + srcPath + "\n");
364:
365: // Set the Java compiler to use
366: if (options.getCompiler() != null) {
367: javac.setCompiler(options.getCompiler());
368: info.append(" compiler=" + options.getCompiler() + "\n");
369: }
370:
371: // Build includes path
372: PatternSet.NameEntry includes = javac.createInclude();
373:
374: includes.setName(ctxt.getJavaPath());
375: info.append(" include=" + ctxt.getJavaPath() + "\n");
376:
377: BuildException be = null;
378:
379: try {
380: if (ctxt.getOptions().getFork()) {
381: javac.execute();
382: } else {
383: synchronized (javacLock) {
384: javac.execute();
385: }
386: }
387: } catch (BuildException e) {
388: be = e;
389: log.error("Javac exception ", e);
390: log.error("Env: " + info.toString());
391: }
392:
393: errorReport.append(logger.getReport());
394:
395: // Stop capturing the System.err output for this thread
396: String errorCapture = SystemLogHandler.unsetThread();
397: if (errorCapture != null) {
398: errorReport.append(System.getProperty("line.separator"));
399: errorReport.append(errorCapture);
400: }
401:
402: if (!ctxt.keepGenerated()) {
403: File javaFile = new File(javaFileName);
404: javaFile.delete();
405: }
406:
407: if (be != null) {
408: String errorReportString = errorReport.toString();
409: log.error("Error compiling file: " + javaFileName + " "
410: + errorReportString);
411: JavacErrorDetail[] javacErrors = errDispatcher
412: .parseJavacErrors(errorReportString, javaFileName,
413: pageNodes);
414: if (javacErrors != null) {
415: errDispatcher.javacError(javacErrors);
416: } else {
417: errDispatcher.javacError(errorReportString, be);
418: }
419: }
420:
421: long t2 = System.currentTimeMillis();
422: if (t2 - t1 > 500) {
423: log.debug("Compiled " + javaFileName + " " + (t2 - t1));
424: }
425:
426: if (ctxt.isPrototypeMode()) {
427: return;
428: }
429:
430: // JSR45 Support
431: if (!options.isSmapSuppressed()) {
432: SmapUtil.installSmap(smap);
433: }
434: }
435:
436: /**
437: * Compile the jsp file from the current engine context
438: */
439: public void compile() throws FileNotFoundException,
440: JasperException, Exception {
441: compile(true);
442: }
443:
444: /**
445: * Compile the jsp file from the current engine context. As an side-
446: * effect, tag files that are referenced by this page are also compiled.
447: * @param compileClass If true, generate both .java and .class file
448: * If false, generate only .java file
449: */
450: public void compile(boolean compileClass)
451: throws FileNotFoundException, JasperException, Exception {
452: compile(compileClass, false);
453: }
454:
455: /**
456: * Compile the jsp file from the current engine context. As an side-
457: * effect, tag files that are referenced by this page are also compiled.
458: *
459: * @param compileClass If true, generate both .java and .class file
460: * If false, generate only .java file
461: * @param jspcMode true if invoked from JspC, false otherwise
462: */
463: public void compile(boolean compileClass, boolean jspcMode)
464: throws FileNotFoundException, JasperException, Exception {
465: if (errDispatcher == null) {
466: this .errDispatcher = new ErrorDispatcher(jspcMode);
467: }
468:
469: try {
470: String[] smap = generateJava();
471: if (compileClass) {
472: generateClass(smap);
473: }
474: } finally {
475: if (tfp != null) {
476: tfp.removeProtoTypeFiles(null);
477: }
478: // Make sure these object which are only used during the
479: // generation and compilation of the JSP page get
480: // dereferenced so that they can be GC'd and reduce the
481: // memory footprint.
482: tfp = null;
483: errDispatcher = null;
484: logger = null;
485: project = null;
486: pageInfo = null;
487: pageNodes = null;
488: if (ctxt.getWriter() != null) {
489: ctxt.getWriter().close();
490: ctxt.setWriter(null);
491: }
492: }
493: }
494:
495: /**
496: * This is a protected method intended to be overridden by
497: * subclasses of Compiler. This is used by the compile method
498: * to do all the compilation.
499: */
500: public boolean isOutDated() {
501: return isOutDated(true);
502: }
503:
504: /**
505: * Determine if a compilation is necessary by checking the time stamp
506: * of the JSP page with that of the corresponding .class or .java file.
507: * If the page has dependencies, the check is also extended to its
508: * dependeants, and so on.
509: * This method can by overidden by a subclasses of Compiler.
510: * @param checkClass If true, check against .class file,
511: * if false, check against .java file.
512: */
513: public boolean isOutDated(boolean checkClass) {
514:
515: boolean outDated = false;
516: String jsp = ctxt.getJspFile();
517:
518: long jspRealLastModified = 0;
519: try {
520: URL jspUrl = ctxt.getResource(jsp);
521: if (jspUrl == null) {
522: ctxt.incrementRemoved();
523: return false;
524: }
525: URLConnection uc = jspUrl.openConnection();
526: jspRealLastModified = uc.getLastModified();
527: uc.getInputStream().close();
528: } catch (Exception e) {
529: e.printStackTrace();
530: return true;
531: }
532:
533: long targetLastModified = 0;
534: File targetFile;
535:
536: if (checkClass) {
537: targetFile = new File(ctxt.getClassFileName());
538: } else {
539: targetFile = new File(ctxt.getServletJavaFileName());
540: }
541:
542: if (!targetFile.exists()) {
543: outDated = true;
544: } else {
545: targetLastModified = targetFile.lastModified();
546: if (checkClass && jsw != null) {
547: jsw.setServletClassLastModifiedTime(targetLastModified);
548: }
549: if (targetLastModified < jspRealLastModified) {
550: if (log.isDebugEnabled()) {
551: log.debug("Compiler: outdated: " + targetFile + " "
552: + targetLastModified);
553: }
554: outDated = true;
555: }
556: }
557:
558: // determine if source dependent files (e.g. includes using include
559: // directives) have been changed.
560: if (jsw == null) {
561: return outDated;
562: }
563:
564: List depends = jsw.getDependants();
565: if (depends == null) {
566: return outDated;
567: }
568:
569: Iterator it = depends.iterator();
570: while (it.hasNext()) {
571: String include = (String) it.next();
572: try {
573: URL includeUrl = ctxt.getResource(include);
574: if (includeUrl == null) {
575: outDated = true;
576: }
577: if (!outDated) {
578:
579: URLConnection includeUconn = includeUrl
580: .openConnection();
581: long includeLastModified = includeUconn
582: .getLastModified();
583: includeUconn.getInputStream().close();
584:
585: if (includeLastModified > targetLastModified) {
586: outDated = true;
587: }
588: }
589: if (outDated) {
590: // Remove any potential Wrappers for tag files
591: ctxt.getRuntimeContext().removeWrapper(include);
592: }
593: } catch (Exception e) {
594: e.printStackTrace();
595: outDated = true;
596: }
597: }
598:
599: return outDated;
600:
601: }
602:
603: /**
604: * Gets the error dispatcher.
605: */
606: public ErrorDispatcher getErrorDispatcher() {
607: return errDispatcher;
608: }
609:
610: /**
611: * Gets the info about the page under compilation
612: */
613: public PageInfo getPageInfo() {
614: return pageInfo;
615: }
616:
617: public JspCompilationContext getCompilationContext() {
618: return ctxt;
619: }
620:
621: /**
622: * Remove generated files
623: */
624: public void removeGeneratedFiles() {
625: try {
626: String classFileName = ctxt.getClassFileName();
627: if (classFileName != null) {
628: File classFile = new File(classFileName);
629: if (log.isDebugEnabled())
630: log.debug("Deleting " + classFile);
631: classFile.delete();
632: }
633: } catch (Exception e) {
634: // Remove as much as possible, ignore possible exceptions
635: }
636: try {
637: String javaFileName = ctxt.getServletJavaFileName();
638: if (javaFileName != null) {
639: File javaFile = new File(javaFileName);
640: if (log.isDebugEnabled())
641: log.debug("Deleting " + javaFile);
642: javaFile.delete();
643: }
644: } catch (Exception e) {
645: // Remove as much as possible, ignore possible exceptions
646: }
647: }
648:
649: public void removeGeneratedClassFiles() {
650: try {
651: String classFileName = ctxt.getClassFileName();
652: if (classFileName != null) {
653: File classFile = new File(classFileName);
654: if (log.isDebugEnabled())
655: log.debug("Deleting " + classFile);
656: classFile.delete();
657: }
658: } catch (Exception e) {
659: // Remove as much as possible, ignore possible exceptions
660: }
661: }
662: }
|