View Javadoc

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.struts2.jasper.compiler;
19  
20  import com.opensymphony.xwork2.util.logging.Logger;
21  import com.opensymphony.xwork2.util.logging.LoggerFactory;
22  import org.apache.struts2.jasper.JasperException;
23  import org.apache.struts2.jasper.JspCompilationContext;
24  import org.apache.struts2.jasper.Options;
25  import org.apache.struts2.jasper.servlet.JspServletWrapper;
26  
27  import java.io.*;
28  import java.net.URL;
29  import java.net.URLConnection;
30  import java.util.Iterator;
31  import java.util.List;
32  
33  /***
34   * Main JSP compiler class. This class uses Ant for compiling.
35   *
36   * @author Anil K. Vijendran
37   * @author Mandar Raje
38   * @author Pierre Delisle
39   * @author Kin-man Chung
40   * @author Remy Maucherat
41   * @author Mark Roth
42   */
43  public abstract class Compiler {
44      private Logger log = LoggerFactory.getLogger(Compiler.class);
45  
46      // ----------------------------------------------------------------- Static
47  
48  
49      // Some javac are not thread safe; use a lock to serialize compilation, 
50      static Object javacLock = new Object();
51  
52  
53      // ----------------------------------------------------- Instance Variables
54  
55  
56      protected JspCompilationContext ctxt;
57  
58      protected ErrorDispatcher errDispatcher;
59      protected PageInfo pageInfo;
60      protected JspServletWrapper jsw;
61      protected TagFileProcessor tfp;
62  
63      protected Options options;
64  
65      protected Node.Nodes pageNodes;
66      // ------------------------------------------------------------ Constructor
67  
68      public void init(JspCompilationContext ctxt, JspServletWrapper jsw) {
69          this.jsw = jsw;
70          this.ctxt = ctxt;
71          this.options = ctxt.getOptions();
72      }
73  
74      // --------------------------------------------------------- Public Methods
75  
76      /***
77       * <p>Retrieves the parsed nodes of the JSP page, if they are available.
78       * May return null.  Used in development mode for generating detailed
79       * error messages.  http://issues.apache.org/bugzilla/show_bug.cgi?id=37062.
80       * </p>
81       */
82      public Node.Nodes getPageNodes() {
83          return this.pageNodes;
84      }
85  
86      /***
87       * Compile the jsp file into equivalent servlet in .java file
88       *
89       * @return a smap for the current JSP page, if one is generated,
90       *         null otherwise
91       */
92      protected String[] generateJava() throws Exception {
93  
94          String[] smapStr = null;
95  
96          long t1, t2, t3, t4;
97  
98          t1 = t2 = t3 = t4 = 0;
99  
100         if (log.isDebugEnabled()) {
101             t1 = System.currentTimeMillis();
102         }
103 
104         // Setup page info area
105         pageInfo = new PageInfo(new BeanRepository(ctxt.getClassLoader(),
106                 errDispatcher),
107                 ctxt.getJspFile());
108 
109         JspConfig jspConfig = options.getJspConfig();
110         JspConfig.JspProperty jspProperty =
111                 jspConfig.findJspProperty(ctxt.getJspFile());
112 
113         /*
114          * If the current uri is matched by a pattern specified in
115          * a jsp-property-group in web.xml, initialize pageInfo with
116          * those properties.
117          */
118         pageInfo.setELIgnored(JspUtil.booleanValue(
119                 jspProperty.isELIgnored()));
120         pageInfo.setScriptingInvalid(JspUtil.booleanValue(
121                 jspProperty.isScriptingInvalid()));
122         if (jspProperty.getIncludePrelude() != null) {
123             pageInfo.setIncludePrelude(jspProperty.getIncludePrelude());
124         }
125         if (jspProperty.getIncludeCoda() != null) {
126             pageInfo.setIncludeCoda(jspProperty.getIncludeCoda());
127         }
128 
129         String javaFileName = ctxt.getServletJavaFileName();
130         ServletWriter writer = null;
131 
132         try {
133             // Setup the ServletWriter
134             String javaEncoding = ctxt.getOptions().getJavaEncoding();
135             ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(1024);
136             OutputStreamWriter osw = null;
137 
138             try {
139                 osw = new OutputStreamWriter(
140                         byteArrayOutputStream, javaEncoding);
141             } catch (UnsupportedEncodingException ex) {
142                 errDispatcher.jspError("jsp.error.needAlternateJavaEncoding",
143                         javaEncoding);
144             }
145 
146             writer = new ServletWriter(new PrintWriter(osw));
147             ctxt.setWriter(writer);
148 
149             // Reset the temporary variable counter for the generator.
150             JspUtil.resetTemporaryVariableName();
151 
152             // Parse the file
153             ParserController parserCtl = new ParserController(ctxt, this);
154             pageNodes = parserCtl.parse(ctxt.getJspFile());
155 
156             if (ctxt.isPrototypeMode()) {
157                 // generate prototype .java file for the tag file
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             Generator.generate(writer, this, pageNodes);
198             writer.close();
199             writer = null;
200 
201             // The writer is only used during the compile, dereference
202             // it in the JspCompilationContext when done to allow it
203             // to be GC'd and save memory.
204             ctxt.setWriter(null);
205             ctxt.setSourceCode(byteArrayOutputStream.toString());
206             if (log.isDebugEnabled()) {
207                 t4 = System.currentTimeMillis();
208                 log.debug("Generated " + javaFileName + " total="
209                         + (t4 - t1) + " generate=" + (t4 - t3)
210                         + " 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     /***
251      * Compile the servlet from .java file to .class file
252      */
253     protected abstract void generateClass(String[] smap)
254             throws FileNotFoundException, JasperException, Exception;
255 
256 
257     /***
258      * Compile the jsp file from the current engine context
259      */
260     public void compile()
261             throws FileNotFoundException, JasperException, Exception {
262         compile(true);
263     }
264 
265     /***
266      * Compile the jsp file from the current engine context.  As an side-
267      * effect, tag files that are referenced by this page are also compiled.
268      *
269      * @param compileClass If true, generate both .java and .class file
270      *                     If false, generate only .java file
271      */
272     public void compile(boolean compileClass)
273             throws FileNotFoundException, JasperException, Exception {
274         compile(compileClass, false);
275     }
276 
277     /***
278      * Compile the jsp file from the current engine context.  As an side-
279      * effect, tag files that are referenced by this page are also compiled.
280      *
281      * @param compileClass If true, generate both .java and .class file
282      *                     If false, generate only .java file
283      * @param jspcMode     true if invoked from JspC, false otherwise
284      */
285     public void compile(boolean compileClass, boolean jspcMode)
286             throws FileNotFoundException, JasperException, Exception {
287         if (errDispatcher == null) {
288             this.errDispatcher = new ErrorDispatcher(jspcMode);
289         }
290 
291         try {
292             String[] smap = generateJava();
293             if (compileClass) {
294                 generateClass(smap);
295             }
296         } finally {
297             if (tfp != null) {
298                 tfp.removeProtoTypeFiles(null);
299             }
300             // Make sure these object which are only used during the
301             // generation and compilation of the JSP page get
302             // dereferenced so that they can be GC'd and reduce the
303             // memory footprint.
304             tfp = null;
305             errDispatcher = null;
306             pageInfo = null;
307 
308             // Only get rid of the pageNodes if in production.
309             // In development mode, they are used for detailed
310             // error messages.
311             // http://issues.apache.org/bugzilla/show_bug.cgi?id=37062
312             if (!this.options.getDevelopment()) {
313                 pageNodes = null;
314             }
315 
316             if (ctxt.getWriter() != null) {
317                 ctxt.getWriter().close();
318                 ctxt.setWriter(null);
319             }
320         }
321     }
322 
323     /***
324      * This is a protected method intended to be overridden by
325      * subclasses of Compiler. This is used by the compile method
326      * to do all the compilation.
327      */
328     public boolean isOutDated() {
329         return isOutDated(true);
330     }
331 
332     /***
333      * Determine if a compilation is necessary by checking the time stamp
334      * of the JSP page with that of the corresponding .class or .java file.
335      * If the page has dependencies, the check is also extended to its
336      * dependeants, and so on.
337      * This method can by overidden by a subclasses of Compiler.
338      *
339      * @param checkClass If true, check against .class file,
340      *                   if false, check against .java file.
341      */
342     public boolean isOutDated(boolean checkClass) {
343 
344         String jsp = ctxt.getJspFile();
345 
346         if (jsw != null
347                 && (ctxt.getOptions().getModificationTestInterval() > 0)) {
348 
349             if (jsw.getLastModificationTest()
350                     + (ctxt.getOptions().getModificationTestInterval() * 1000)
351                     > System.currentTimeMillis()) {
352                 return false;
353             } else {
354                 jsw.setLastModificationTest(System.currentTimeMillis());
355             }
356         }
357 
358         long jspRealLastModified = 0;
359         try {
360             URL jspUrl = ctxt.getResource(jsp);
361             if (jspUrl == null) {
362                 ctxt.incrementRemoved();
363                 return false;
364             }
365             URLConnection uc = jspUrl.openConnection();
366             jspRealLastModified = uc.getLastModified();
367             uc.getInputStream().close();
368         } catch (Exception e) {
369             e.printStackTrace();
370             return true;
371         }
372 
373         long targetLastModified = 0;
374         File targetFile;
375 
376         if (checkClass) {
377             targetFile = new File(ctxt.getClassFileName());
378         } else {
379             targetFile = new File(ctxt.getServletJavaFileName());
380         }
381 
382         if (!targetFile.exists()) {
383             return true;
384         }
385 
386         targetLastModified = targetFile.lastModified();
387         if (checkClass && jsw != null) {
388             jsw.setServletClassLastModifiedTime(targetLastModified);
389         }
390         if (targetLastModified < jspRealLastModified) {
391             if (log.isDebugEnabled()) {
392                 log.debug("Compiler: outdated: " + targetFile + " " +
393                         targetLastModified);
394             }
395             return true;
396         }
397 
398         // determine if source dependent files (e.g. includes using include
399         // directives) have been changed.
400         if (jsw == null) {
401             return false;
402         }
403 
404         List depends = jsw.getDependants();
405         if (depends == null) {
406             return false;
407         }
408 
409         Iterator it = depends.iterator();
410         while (it.hasNext()) {
411             String include = (String) it.next();
412             try {
413                 URL includeUrl = ctxt.getResource(include);
414                 if (includeUrl == null) {
415                     return true;
416                 }
417 
418                 URLConnection includeUconn = includeUrl.openConnection();
419                 long includeLastModified = includeUconn.getLastModified();
420                 includeUconn.getInputStream().close();
421 
422                 if (includeLastModified > targetLastModified) {
423                     return true;
424                 }
425             } catch (Exception e) {
426                 e.printStackTrace();
427                 return true;
428             }
429         }
430 
431         return false;
432 
433     }
434 
435 
436     /***
437      * Gets the error dispatcher.
438      */
439     public ErrorDispatcher getErrorDispatcher() {
440         return errDispatcher;
441     }
442 
443 
444     /***
445      * Gets the info about the page under compilation
446      */
447     public PageInfo getPageInfo() {
448         return pageInfo;
449     }
450 
451 
452     public JspCompilationContext getCompilationContext() {
453         return ctxt;
454     }
455 
456 
457     /***
458      * Remove generated files
459      */
460     public void removeGeneratedFiles() {
461         try {
462             String classFileName = ctxt.getClassFileName();
463             if (classFileName != null) {
464                 File classFile = new File(classFileName);
465                 if (log.isDebugEnabled())
466                     log.debug("Deleting " + classFile);
467                 classFile.delete();
468             }
469         } catch (Exception e) {
470             // Remove as much as possible, ignore possible exceptions
471         }
472         try {
473             String javaFileName = ctxt.getServletJavaFileName();
474             if (javaFileName != null) {
475                 File javaFile = new File(javaFileName);
476                 if (log.isDebugEnabled())
477                     log.debug("Deleting " + javaFile);
478                 javaFile.delete();
479             }
480         } catch (Exception e) {
481             // Remove as much as possible, ignore possible exceptions
482         }
483     }
484 
485     public void removeGeneratedClassFiles() {
486         try {
487             String classFileName = ctxt.getClassFileName();
488             if (classFileName != null) {
489                 File classFile = new File(classFileName);
490                 if (log.isDebugEnabled())
491                     log.debug("Deleting " + classFile);
492                 classFile.delete();
493             }
494         } catch (Exception e) {
495             // Remove as much as possible, ignore possible exceptions
496         }
497     }
498 }