View Javadoc

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