View Javadoc

1   //========================================================================
2   //$Id: JspcMojo.java 2820 2008-05-01 07:40:47Z janb $
3   //Copyright 2006 Mort Bay Consulting Pty. Ltd.
4   //------------------------------------------------------------------------
5   //Licensed under the Apache License, Version 2.0 (the "License");
6   //you may not use this file except in compliance with the License.
7   //You may obtain a copy of the License at 
8   //http://www.apache.org/licenses/LICENSE-2.0
9   //Unless required by applicable law or agreed to in writing, software
10  //distributed under the License is distributed on an "AS IS" BASIS,
11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  //See the License for the specific language governing permissions and
13  //limitations under the License.
14  //========================================================================
15  
16  package org.mortbay.jetty.jspc.plugin;
17  
18  import org.apache.jasper.JspC;
19  import org.apache.maven.artifact.Artifact;
20  import org.apache.maven.plugin.AbstractMojo;
21  import org.apache.maven.plugin.MojoExecutionException;
22  import org.apache.maven.plugin.MojoFailureException;
23  import org.apache.maven.project.MavenProject;
24  import org.codehaus.plexus.util.FileUtils;
25  import org.codehaus.plexus.util.StringUtils;
26  import org.mortbay.util.IO;
27  
28  import java.io.BufferedReader;
29  import java.io.File;
30  import java.io.FileReader;
31  import java.io.FileWriter;
32  import java.io.FilenameFilter;
33  import java.io.PrintWriter;
34  import java.net.URL;
35  import java.net.URLClassLoader;
36  import java.util.ArrayList;
37  import java.util.Iterator;
38  import java.util.List;
39  
40  /**
41   * <p>
42   * This goal will compile jsps for a webapp so that they can be included in a
43   * war.
44   * </p>
45   * <p>
46   * At runtime, the plugin will use the jsp2.0 jspc compiler if you are running
47   * on a 1.4 or lower jvm. If you are using a 1.5 jvm, then the jsp2.1 compiler
48   * will be selected. (this is the same behaviour as the <a
49   * href="http://jetty.mortbay.org/maven-plugin">jetty plugin</a> for executing
50   * webapps).
51   * </p>
52   * <p>
53   * Note that the same java compiler will be used as for on-the-fly compiled
54   * jsps, which will be the Eclipse java compiler.
55   * </p>
56   * 
57   * <p>
58   * See <a
59   * href="http://docs.codehaus.org/display/JETTY/Maven+Jetty+Jspc+Plugin">Usage
60   * Guide</a> for instructions on using this plugin.
61   * </p>
62   * 
63   * @author janb
64   * 
65   * @goal jspc
66   * @phase process-classes
67   * @requiresDependencyResolution compile
68   * @description Runs jspc compiler to produce .java and .class files
69   */
70  public class JspcMojo extends AbstractMojo
71  {
72      public static final String END_OF_WEBAPP = "</web-app>";
73  
74  
75      /**
76       * The maven project.
77       * 
78       * @parameter expression="${project}"
79       * @required
80       * @readonly
81       */
82      private MavenProject project;
83  
84      /**
85       * File into which to generate the &lt;servlet&gt; and
86       * &lt;servlet-mapping&gt; tags for the compiled jsps
87       * 
88       * @parameter expression="${basedir}/target/webfrag.xml"
89       */
90      private String webXmlFragment;
91  
92      /**
93       * Optional. A marker string in the src web.xml file which indicates where
94       * to merge in the generated web.xml fragment. Note that the marker string
95       * will NOT be preserved during the insertion. Can be left blank, in which
96       * case the generated fragment is inserted just before the &lt;/web-app&gt;
97       * line
98       * 
99       * @parameter
100      */
101     private String insertionMarker;
102 
103     /**
104      * Merge the generated fragment file with the web.xml from
105      * webAppSourceDirectory. The merged file will go into the same directory as
106      * the webXmlFragment.
107      * 
108      * @parameter expression="true"
109      */
110     private boolean mergeFragment;
111 
112     /**
113      * The destination directory into which to put the compiled jsps.
114      * 
115      * @parameter expression="${basedir}/target/classes"
116      */
117     private String generatedClasses;
118 
119     /**
120      * Controls whether or not .java files generated during compilation will be
121      * preserved.
122      * 
123      * @parameter expression="false"
124      */
125     private boolean keepSources;
126 
127     /**
128      * Default root package for all generated classes
129      * 
130      * @parameter expression="jsp"
131      */
132     private String packageRoot;
133 
134     /**
135      * Root directory for all html/jsp etc files
136      * 
137      * @parameter expression="${basedir}/src/main/webapp"
138      * @required
139      */
140     private String webAppSourceDirectory;
141 
142 
143     /**
144      * The comma separated list of patterns for file extensions to be processed. By default
145      * will include all .jsp and .jspx files.
146      * 
147      * @parameter default-value="**\/*.jsp, **\/*.jspx"
148      */
149     private String includes;
150 
151     /**
152      * The comma separated list of file name patters to exclude from compilation.
153      * 
154      * @parameter default_value="**\/.svn\/**";
155      */
156     private String excludes;
157 
158     /**
159      * The location of the compiled classes for the webapp
160      * 
161      * @parameter expression="${project.build.outputDirectory}"
162      */
163     private File classesDirectory;
164 
165     /**
166      * Whether or not to output more verbose messages during compilation.
167      * 
168      * @parameter expression="false";
169      */
170     private boolean verbose;
171 
172     /**
173      * If true, validates tlds when parsing.
174      * 
175      * @parameter expression="false";
176      */
177     private boolean validateXml;
178 
179     /**
180      * The encoding scheme to use.
181      * 
182      * @parameter expression="UTF-8"
183      */
184     private String javaEncoding;
185 
186     /**
187      * Whether or not to generate JSR45 compliant debug info
188      * 
189      * @parameter expression="true";
190      */
191     private boolean suppressSmap;
192 
193     /**
194      * Whether or not to ignore precompilation errors caused by jsp fragments.
195      * 
196      * @parameter expression="false"
197      */
198     private boolean ignoreJspFragmentErrors;
199 
200     /**
201      * Allows a prefix to be appended to the standard schema locations so that
202      * they can be loaded from elsewhere.
203      * 
204      * @parameter
205      */
206     private String schemaResourcePrefix;
207     
208 
209     public void execute() throws MojoExecutionException, MojoFailureException
210     {
211         if (getLog().isDebugEnabled())
212         {
213             getLog().info("verbose=" + verbose);
214             getLog().info("webAppSourceDirectory=" + webAppSourceDirectory);
215             getLog().info("generatedClasses=" + generatedClasses);
216             getLog().info("webXmlFragment=" + webXmlFragment);
217             getLog().info("validateXml=" + validateXml);
218             getLog().info("packageRoot=" + packageRoot);
219             getLog().info("javaEncoding=" + javaEncoding);
220             getLog().info("insertionMarker="+ (insertionMarker == null || insertionMarker.equals("") ? END_OF_WEBAPP : insertionMarker));
221             getLog().info("keepSources=" + keepSources);
222             getLog().info("mergeFragment=" + mergeFragment);
223             getLog().info("suppressSmap=" + suppressSmap);
224             getLog().info("ignoreJspFragmentErrors=" + ignoreJspFragmentErrors);
225             getLog().info("schemaResourcePrefix=" + schemaResourcePrefix);
226         }
227         try
228         {
229             prepare();
230             compile();
231             cleanupSrcs();
232             mergeWebXml();
233         }
234         catch (Exception e)
235         {
236             throw new MojoFailureException(e, "Failure processing jsps","Failure processing jsps");
237         }
238     }
239 
240     public void compile() throws Exception
241     {
242         ClassLoader currentClassLoader = Thread.currentThread()
243         .getContextClassLoader();
244 
245         ArrayList urls = new ArrayList();
246         setUpClassPath(urls);
247         URLClassLoader ucl = new URLClassLoader((URL[]) urls.toArray(new URL[0]), currentClassLoader);
248         StringBuffer classpathStr = new StringBuffer();
249 
250         for (int i = 0; i < urls.size(); i++)
251         {
252             if (getLog().isDebugEnabled())
253                 getLog().debug("webappclassloader contains: " + urls.get(i));
254             classpathStr.append(((URL) urls.get(i)).getFile());
255             if (getLog().isDebugEnabled())
256                 getLog().debug(
257                         "added to classpath: " + ((URL) urls.get(i)).getFile());
258             classpathStr.append(System.getProperty("path.separator"));
259         }
260 
261         Thread.currentThread().setContextClassLoader(ucl);
262 
263         JspC jspc = new JspC();
264         jspc.setWebXmlFragment(webXmlFragment);
265         jspc.setUriroot(webAppSourceDirectory);
266         jspc.setPackage(packageRoot);
267         jspc.setOutputDir(generatedClasses);
268         jspc.setValidateXml(validateXml);
269         jspc.setClassPath(classpathStr.toString());
270         jspc.setCompile(true);
271         jspc.setSmapSuppressed(suppressSmap);
272         jspc.setSmapDumped(!suppressSmap);
273         jspc.setJavaEncoding(javaEncoding);
274 
275         // JspC#setExtensions() does not exist, so 
276         // always set concrete list of files that will be processed.
277         String jspFiles = getJspFiles(webAppSourceDirectory);
278         System.err.println("Compiling "+jspFiles);
279         System.err.println("Includes="+includes);
280         System.err.println("Excludes="+excludes);
281         jspc.setJspFiles(jspFiles);
282         if (verbose)
283         {
284             getLog().info("Files selected to precompile: " + jspFiles);
285         }
286         
287 
288         try
289         {
290             jspc.setIgnoreJspFragmentErrors(ignoreJspFragmentErrors);
291         }
292         catch (NoSuchMethodError e)
293         {
294             getLog().debug("Tomcat Jasper does not support configuration option 'ignoreJspFragmentErrors': ignored");
295         }
296 
297         try
298         {
299             if (schemaResourcePrefix != null)
300                 jspc.setSchemaResourcePrefix(schemaResourcePrefix);
301         }
302         catch (NoSuchMethodError e)
303         {
304             getLog().debug("Tomcat Jasper does not support configuration option 'schemaResourcePrefix': ignored");
305         }
306         if (verbose)
307             jspc.setVerbose(99);
308         else
309             jspc.setVerbose(0);
310 
311         jspc.execute();
312 
313         Thread.currentThread().setContextClassLoader(currentClassLoader);
314     }
315 
316     private String getJspFiles(String webAppSourceDirectory)
317     throws Exception
318     {
319         List fileNames =  FileUtils.getFileNames(new File(webAppSourceDirectory),includes, excludes, false);
320         return StringUtils.join(fileNames.toArray(new String[0]), ",");
321 
322     }
323 
324     /**
325      * Until Jasper supports the option to generate the srcs in a different dir
326      * than the classes, this is the best we can do.
327      * 
328      * @throws Exception
329      */
330     public void cleanupSrcs() throws Exception
331     {
332         // delete the .java files - depending on keepGenerated setting
333         if (!keepSources)
334         {
335             String packageRootDirectory = packageRoot.replace('.',
336                     File.separatorChar);
337 
338             File generatedClassesDir = new File(generatedClasses
339                     + File.separatorChar + packageRootDirectory);
340 
341             File[] srcFiles = generatedClassesDir
342             .listFiles(new FilenameFilter()
343             {
344                 public boolean accept(File dir, String name)
345                 {
346                     if (name == null) return false;
347                     if (name.trim().equals("")) return false;
348                     if (name.endsWith(".java")) return true;
349 
350                     return false;
351                 }
352             });
353 
354             for (int i = 0; (srcFiles != null) && (i < srcFiles.length); i++)
355             {
356                 srcFiles[i].delete();
357             }
358         }
359     }
360 
361     /**
362      * Take the web fragment and put it inside a copy of the web.xml file from
363      * the webAppSourceDirectory.
364      * 
365      * You can specify the insertion point by specifying the string in the
366      * insertionMarker configuration entry.
367      * 
368      * If you dont specify the insertionMarker, then the fragment will be
369      * inserted at the end of the file just before the &lt;/webapp&gt;
370      * 
371      * @throws Exception
372      */
373     public void mergeWebXml() throws Exception
374     {
375         if (mergeFragment)
376         {
377             // open the src web.xml
378             File webXml = new File(webAppSourceDirectory + "/WEB-INF/web.xml");
379             if (!webXml.exists())
380             {
381                 getLog()
382                 .info(
383                         webAppSourceDirectory
384                         + "/WEB-INF/web.xml does not exist, cannot merge with generated fragment");
385                 return;
386             }
387 
388             File fragmentWebXml = new File(webXmlFragment);
389             if (!fragmentWebXml.exists())
390             {
391                 getLog().info("No fragment web.xml file generated");
392             }
393             File mergedWebXml = new File(fragmentWebXml.getParentFile(),
394             "web.xml");
395             BufferedReader webXmlReader = new BufferedReader(new FileReader(
396                     webXml));
397             PrintWriter mergedWebXmlWriter = new PrintWriter(new FileWriter(
398                     mergedWebXml));
399 
400             // read up to the insertion marker or the </webapp> if there is no
401             // marker
402             boolean atInsertPoint = false;
403             boolean atEOF = false;
404             String marker = (insertionMarker == null
405                     || insertionMarker.equals("") ? END_OF_WEBAPP : insertionMarker);
406             while (!atInsertPoint && !atEOF)
407             {
408                 String line = webXmlReader.readLine();
409                 if (line == null)
410                     atEOF = true;
411                 else if (line.indexOf(marker) >= 0)
412                 {
413                     atInsertPoint = true;
414                 }
415                 else
416                 {
417                     mergedWebXmlWriter.println(line);
418                 }
419             }
420 
421             // put in the generated fragment
422             BufferedReader fragmentWebXmlReader = new BufferedReader(
423                     new FileReader(fragmentWebXml));
424             IO.copy(fragmentWebXmlReader, mergedWebXmlWriter);
425 
426             // if we inserted just before the </web-app>, put it back in
427             if (marker.equals(END_OF_WEBAPP))
428                 mergedWebXmlWriter.println(END_OF_WEBAPP);
429 
430             // copy in the rest of the original web.xml file
431             IO.copy(webXmlReader, mergedWebXmlWriter);
432 
433             webXmlReader.close();
434             mergedWebXmlWriter.close();
435             fragmentWebXmlReader.close();
436         }
437     }
438 
439     private void prepare() throws Exception
440     {
441         // For some reason JspC doesn't like it if the dir doesn't
442         // already exist and refuses to create the web.xml fragment
443         File generatedSourceDirectoryFile = new File(generatedClasses);
444         if (!generatedSourceDirectoryFile.exists())
445             generatedSourceDirectoryFile.mkdirs();
446     }
447 
448     /**
449      * Set up the execution classpath for Jasper.
450      * 
451      * Put everything in the classesDirectory and all of the dependencies on the
452      * classpath.
453      * 
454      * @param urls a list to which to add the urls of the dependencies
455      * @throws Exception
456      */
457     private void setUpClassPath(List urls) throws Exception
458     {
459         String classesDir = classesDirectory.getCanonicalPath();
460         classesDir = classesDir
461         + (classesDir.endsWith(File.pathSeparator) ? "" : File.separator);
462         urls.add(new File(classesDir).toURL());
463 
464         if (getLog().isDebugEnabled())
465             getLog().debug("Adding to classpath classes dir: " + classesDir);
466 
467         for (Iterator iter = project.getArtifacts().iterator(); iter.hasNext();)
468         {
469             Artifact artifact = (Artifact) iter.next();
470 
471             // Include runtime and compile time libraries
472             if (!Artifact.SCOPE_TEST.equals(artifact.getScope()))
473             {
474                 String filePath = artifact.getFile().getCanonicalPath();
475                 if (getLog().isDebugEnabled())
476                     getLog().debug(
477                             "Adding to classpath dependency file: " + filePath);
478 
479                 urls.add(artifact.getFile().toURL());
480             }
481         }
482     }
483 }