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 }