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 }