Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: d5645c459f2a3b8de8bc49dd65588b3c4357ff46 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
//
//  ========================================================================
//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.jspc.plugin;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.jasper.JspC;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.StringUtils;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.PatternMatcher;
import org.eclipse.jetty.util.resource.Resource;

/**
 * <p>
 * This goal will compile jsps for a webapp so that they can be included in a
 * war.
 * </p>
 * <p>
 * At runtime, the plugin will use the jsp2.0 jspc compiler if you are running
 * on a 1.4 or lower jvm. If you are using a 1.5 jvm, then the jsp2.1 compiler
 * will be selected. (this is the same behaviour as the <a
 * href="http://jetty.mortbay.org/maven-plugin">jetty plugin</a> for executing
 * webapps).
 * </p>
 * <p>
 * Note that the same java compiler will be used as for on-the-fly compiled
 * jsps, which will be the Eclipse java compiler.
 * </p>
 * 
 * <p>
 * See <a
 * href="http://docs.codehaus.org/display/JETTY/Maven+Jetty+Jspc+Plugin">Usage
 * Guide</a> for instructions on using this plugin.
 * </p>
 * 
 * @author janb
 * 
 * @goal jspc
 * @phase process-classes
 * @requiresDependencyResolution compile
 * @description Runs jspc compiler to produce .java and .class files
 */
public class JspcMojo extends AbstractMojo
{
    public static final String END_OF_WEBAPP = "</web-app>";


    /**
     * Whether or not to include dependencies on the plugin's classpath with &lt;scope&gt;provided&lt;/scope&gt;
     * Use WITH CAUTION as you may wind up with duplicate jars/classes.
     * 
     * @since jetty-7.6.3
     * @parameter  default-value="false"
     */
    private boolean useProvidedScope;
    
    /**
     * The artifacts for the project.
     * 
     * @since jetty-7.6.3
     * @parameter expression="${project.artifacts}"
     * @readonly
     */
    private Set projectArtifacts;
    
    
    /**
     * The maven project.
     * 
     * @parameter expression="${project}"
     * @required
     * @readonly
     */
    private MavenProject project;

    

    /**
     * The artifacts for the plugin itself.
     * 
     * @parameter expression="${plugin.artifacts}"
     * @readonly
     */
    private List pluginArtifacts;
    
    
    /**
     * File into which to generate the &lt;servlet&gt; and
     * &lt;servlet-mapping&gt; tags for the compiled jsps
     * 
     * @parameter default-value="${basedir}/target/webfrag.xml"
     */
    private String webXmlFragment;

    /**
     * Optional. A marker string in the src web.xml file which indicates where
     * to merge in the generated web.xml fragment. Note that the marker string
     * will NOT be preserved during the insertion. Can be left blank, in which
     * case the generated fragment is inserted just before the &lt;/web-app&gt;
     * line
     * 
     * @parameter
     */
    private String insertionMarker;

    /**
     * Merge the generated fragment file with the web.xml from
     * webAppSourceDirectory. The merged file will go into the same directory as
     * the webXmlFragment.
     * 
     * @parameter default-value="true"
     */
    private boolean mergeFragment;

    /**
     * The destination directory into which to put the compiled jsps.
     * 
     * @parameter default-value="${project.build.outputDirectory}"
     */
    private String generatedClasses;

    /**
     * Controls whether or not .java files generated during compilation will be
     * preserved.
     * 
     * @parameter default-value="false"
     */
    private boolean keepSources;

    /**
     * Default root package for all generated classes
     * 
     * @parameter default-value="jsp"
     */
    private String packageRoot;

    /**
     * Root directory for all html/jsp etc files
     * 
     * @parameter default-value="${basedir}/src/main/webapp"
     * 
     */
    private String webAppSourceDirectory;
    
   
    
    /**
     * Location of web.xml. Defaults to src/main/webapp/web.xml.
     * @parameter default-value="${basedir}/src/main/webapp/WEB-INF/web.xml"
     */
    private String webXml;


    /**
     * The comma separated list of patterns for file extensions to be processed. By default
     * will include all .jsp and .jspx files.
     * 
     * @parameter default-value="**\/*.jsp, **\/*.jspx"
     */
    private String includes;

    /**
     * The comma separated list of file name patters to exclude from compilation.
     * 
     * @parameter default_value="**\/.svn\/**";
     */
    private String excludes;

    /**
     * The location of the compiled classes for the webapp
     * 
     * @parameter expression="${project.build.outputDirectory}"
     */
    private File classesDirectory;

    /**
     * Whether or not to output more verbose messages during compilation.
     * 
     * @parameter default-value="false";
     */
    private boolean verbose;

    /**
     * If true, validates tlds when parsing.
     * 
     * @parameter default-value="false";
     */
    private boolean validateXml;

    /**
     * The encoding scheme to use.
     * 
     * @parameter default-value="UTF-8"
     */
    private String javaEncoding;

    /**
     * Whether or not to generate JSR45 compliant debug info
     * 
     * @parameter default-value="true";
     */
    private boolean suppressSmap;

    /**
     * Whether or not to ignore precompilation errors caused by jsp fragments.
     * 
     * @parameter default-value="false"
     */
    private boolean ignoreJspFragmentErrors;

    /**
     * Allows a prefix to be appended to the standard schema locations so that
     * they can be loaded from elsewhere.
     * 
     * @parameter
     */
    private String schemaResourcePrefix;
    
    /**
     * Patterns of jars on the system path that contain tlds. Use | to separate each pattern.
     * 
     * @parameter default-value=".*taglibs[^/]*\.jar|.*jstl-impl[^/]*\.jar$
     */
    private String tldJarNamePatterns;



    /**
     * Should white spaces in template text between actions or directives be trimmed? Defaults to false.
     * @parameter
     */
    private boolean trimSpaces = false;
    

    public void execute() throws MojoExecutionException, MojoFailureException
    {
        if (getLog().isDebugEnabled())
        {
            getLog().info("verbose=" + verbose);
            getLog().info("webAppSourceDirectory=" + webAppSourceDirectory);
            getLog().info("generatedClasses=" + generatedClasses);
            getLog().info("webXmlFragment=" + webXmlFragment);
            getLog().info("webXml="+webXml);
            getLog().info("validateXml=" + validateXml);
            getLog().info("packageRoot=" + packageRoot);
            getLog().info("javaEncoding=" + javaEncoding);
            getLog().info("insertionMarker="+ (insertionMarker == null || insertionMarker.equals("") ? END_OF_WEBAPP : insertionMarker));
            getLog().info("keepSources=" + keepSources);
            getLog().info("mergeFragment=" + mergeFragment);
            getLog().info("suppressSmap=" + suppressSmap);
            getLog().info("ignoreJspFragmentErrors=" + ignoreJspFragmentErrors);
            getLog().info("schemaResourcePrefix=" + schemaResourcePrefix);
            getLog().info("trimSpaces=" + trimSpaces);
        }
        try
        {
            prepare();
            compile();
            cleanupSrcs();
            mergeWebXml();
        }
        catch (Exception e)
        {
            throw new MojoExecutionException("Failure processing jsps", e);
        }
    }

    public void compile() throws Exception
    {
        ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();

        //set up the classpath of the webapp
        List<URL> webAppUrls = setUpWebAppClassPath();
        
        //set up the classpath of the container (ie jetty and jsp jars)
        String sysClassPath = setUpSysClassPath();
        
        //get the list of system classpath jars that contain tlds
        List<URL> tldJarUrls = getSystemJarsWithTlds();
        
        for (URL u:tldJarUrls)
        {
            if (getLog().isDebugEnabled())
                getLog().debug(" sys jar with tlds: "+u);
            webAppUrls.add(u);
        }

      
        //use the classpaths as the classloader
        URLClassLoader webAppClassLoader = new URLClassLoader((URL[]) webAppUrls.toArray(new URL[0]), currentClassLoader);
        StringBuffer webAppClassPath = new StringBuffer();

        for (int i = 0; i < webAppUrls.size(); i++)
        {
            if (getLog().isDebugEnabled())
                getLog().debug("webappclassloader contains: " + webAppUrls.get(i));                
            webAppClassPath.append(new File(webAppUrls.get(i).toURI()).getCanonicalPath());
            if (getLog().isDebugEnabled())
                getLog().debug("added to classpath: " + ((URL) webAppUrls.get(i)).getFile());
            if (i+1<webAppUrls.size())
                webAppClassPath.append(System.getProperty("path.separator"));
        }

        Thread.currentThread().setContextClassLoader(webAppClassLoader);

        JspC jspc = new JspC();
        jspc.setWebXmlFragment(webXmlFragment);
        jspc.setUriroot(webAppSourceDirectory);
        jspc.setPackage(packageRoot);
        jspc.setOutputDir(generatedClasses);
        jspc.setValidateXml(validateXml);
        jspc.setClassPath(webAppClassPath.toString());
        jspc.setCompile(true);
        jspc.setSmapSuppressed(suppressSmap);
        jspc.setSmapDumped(!suppressSmap);
        jspc.setJavaEncoding(javaEncoding);
        jspc.setTrimSpaces(trimSpaces);
        jspc.setSystemClassPath(sysClassPath);
        
        

        // JspC#setExtensions() does not exist, so 
        // always set concrete list of files that will be processed.
        String jspFiles = getJspFiles(webAppSourceDirectory);
        getLog().info("Compiling "+jspFiles);
        getLog().info("Includes="+includes);
        getLog().info("Excludes="+excludes);
        jspc.setJspFiles(jspFiles);
        if (verbose)
        {
            getLog().info("Files selected to precompile: " + jspFiles);
        }
        

        try
        {
            jspc.setIgnoreJspFragmentErrors(ignoreJspFragmentErrors);
        }
        catch (NoSuchMethodError e)
        {
            getLog().debug("Tomcat Jasper does not support configuration option 'ignoreJspFragmentErrors': ignored");
        }

        try
        {
            if (schemaResourcePrefix != null)
                jspc.setSchemaResourcePrefix(schemaResourcePrefix);
        }
        catch (NoSuchMethodError e)
        {
            getLog().debug("Tomcat Jasper does not support configuration option 'schemaResourcePrefix': ignored");
        }
        if (verbose)
            jspc.setVerbose(99);
        else
            jspc.setVerbose(0);

        jspc.execute();

        Thread.currentThread().setContextClassLoader(currentClassLoader);
    }

    private String getJspFiles(String webAppSourceDirectory)
    throws Exception
    {
        List fileNames =  FileUtils.getFileNames(new File(webAppSourceDirectory),includes, excludes, false);
        return StringUtils.join(fileNames.toArray(new String[0]), ",");

    }

    /**
     * Until Jasper supports the option to generate the srcs in a different dir
     * than the classes, this is the best we can do.
     * 
     * @throws Exception
     */
    public void cleanupSrcs() throws Exception
    {
        // delete the .java files - depending on keepGenerated setting
        if (!keepSources)
        {
            File generatedClassesDir = new File(generatedClasses);

            if(generatedClassesDir.exists() && generatedClassesDir.isDirectory())
            {
                delete(generatedClassesDir, new FileFilter()
                {
                    public boolean accept(File f)
                    {
                        return f.isDirectory() || f.getName().endsWith(".java");
                    }                
                });
            }
        }
    }
    
    static void delete(File dir, FileFilter filter)
    {
        File[] files = dir.listFiles(filter);
        for(int i=0; i<files.length; i++)
        {
            File f = files[i];
            if(f.isDirectory())
                delete(f, filter);
            else
                f.delete();
        }
    }

    /**
     * Take the web fragment and put it inside a copy of the web.xml.
     * 
     * You can specify the insertion point by specifying the string in the
     * insertionMarker configuration entry.
     * 
     * If you dont specify the insertionMarker, then the fragment will be
     * inserted at the end of the file just before the &lt;/webapp&gt;
     * 
     * @throws Exception
     */
    public void mergeWebXml() throws Exception
    {
        if (mergeFragment)
        {
            // open the src web.xml
            File webXml = getWebXmlFile();
           
            if (!webXml.exists())
            {
                getLog().info(webXml.toString() + " does not exist, cannot merge with generated fragment");
                return;
            }

            File fragmentWebXml = new File(webXmlFragment);
            if (!fragmentWebXml.exists())
            {
                getLog().info("No fragment web.xml file generated");
            }
            File mergedWebXml = new File(fragmentWebXml.getParentFile(),
            "web.xml");
            BufferedReader webXmlReader = new BufferedReader(new FileReader(
                    webXml));
            PrintWriter mergedWebXmlWriter = new PrintWriter(new FileWriter(
                    mergedWebXml));

            // read up to the insertion marker or the </webapp> if there is no
            // marker
            boolean atInsertPoint = false;
            boolean atEOF = false;
            String marker = (insertionMarker == null
                    || insertionMarker.equals("") ? END_OF_WEBAPP : insertionMarker);
            while (!atInsertPoint && !atEOF)
            {
                String line = webXmlReader.readLine();
                if (line == null)
                    atEOF = true;
                else if (line.indexOf(marker) >= 0)
                {
                    atInsertPoint = true;
                }
                else
                {
                    mergedWebXmlWriter.println(line);
                }
            }

            // put in the generated fragment
            BufferedReader fragmentWebXmlReader = new BufferedReader(
                    new FileReader(fragmentWebXml));
            IO.copy(fragmentWebXmlReader, mergedWebXmlWriter);

            // if we inserted just before the </web-app>, put it back in
            if (marker.equals(END_OF_WEBAPP))
                mergedWebXmlWriter.println(END_OF_WEBAPP);

            // copy in the rest of the original web.xml file
            IO.copy(webXmlReader, mergedWebXmlWriter);

            webXmlReader.close();
            mergedWebXmlWriter.close();
            fragmentWebXmlReader.close();
        }
    }

    private void prepare() throws Exception
    {
        // For some reason JspC doesn't like it if the dir doesn't
        // already exist and refuses to create the web.xml fragment
        File generatedSourceDirectoryFile = new File(generatedClasses);
        if (!generatedSourceDirectoryFile.exists())
            generatedSourceDirectoryFile.mkdirs();
    }

    /**
     * Set up the execution classpath for Jasper.
     * 
     * Put everything in the classesDirectory and all of the dependencies on the
     * classpath.
     * 
     * @returns a list of the urls of the dependencies
     * @throws Exception
     */
    private List<URL> setUpWebAppClassPath() throws Exception
    {
        //add any classes from the webapp
        List<URL> urls = new ArrayList<URL>();
        String classesDir = classesDirectory.getCanonicalPath();
        classesDir = classesDir + (classesDir.endsWith(File.pathSeparator) ? "" : File.separator);
        urls.add(Resource.toURL(new File(classesDir)));

        if (getLog().isDebugEnabled())
            getLog().debug("Adding to classpath classes dir: " + classesDir);

        //add the dependencies of the webapp (which will form WEB-INF/lib)
        for (Iterator<Artifact> iter = project.getArtifacts().iterator(); iter.hasNext();)
        {
            Artifact artifact = (Artifact)iter.next();

            // Include runtime and compile time libraries
            if (!Artifact.SCOPE_TEST.equals(artifact.getScope()) && !Artifact.SCOPE_PROVIDED.equals(artifact.getScope()))
            {
                String filePath = artifact.getFile().getCanonicalPath();
                if (getLog().isDebugEnabled())
                    getLog().debug("Adding to classpath dependency file: " + filePath);

                urls.add(Resource.toURL(artifact.getFile()));
            }
        }
        return urls;
    }
    
    
    private String setUpSysClassPath () throws Exception
    {
        StringBuffer buff = new StringBuffer();
        
        //Put each of the plugin's artifacts onto the system classpath for jspc
        for (Iterator<Artifact> iter = pluginArtifacts.iterator(); iter.hasNext(); )
        {
            Artifact pluginArtifact = iter.next();
            if ("jar".equalsIgnoreCase(pluginArtifact.getType()))
            {
                if (getLog().isDebugEnabled()) { getLog().debug("Adding plugin artifact "+pluginArtifact);}
                buff.append(pluginArtifact.getFile().getAbsolutePath());
                if (iter.hasNext())
                    buff.append(File.pathSeparator);
            }
        }
        
        
        if (useProvidedScope)
        {
            for ( Iterator<Artifact> iter = projectArtifacts.iterator(); iter.hasNext(); )
            {                   
                Artifact artifact = iter.next();
                if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()))
                {
                    //test to see if the provided artifact was amongst the plugin artifacts
                    String path = artifact.getFile().getAbsolutePath();
                    if (! buff.toString().contains(path))
                    {
                        if (buff.length() != 0)
                            buff.append(File.pathSeparator);
                        buff.append(path);
                        if (getLog().isDebugEnabled()) { getLog().debug("Adding provided artifact: "+artifact);}
                    }  
                    else
                    {
                        if (getLog().isDebugEnabled()) { getLog().debug("Skipping provided artifact: "+artifact);}
                    }
                }
            }
        }

        return buff.toString();
    }

    
    /**
     * Glassfish jsp requires that we set up the list of system jars that have
     * tlds in them.
     * 
     * This method is a little fragile, as it relies on knowing that the jstl jars
     * are the only ones in the system path that contain tlds.
     * @return
     * @throws Exception
     */
    private List<URL> getSystemJarsWithTlds() throws Exception
    {
        final List<URL> list = new ArrayList<URL>();
        List<URI> artifactUris = new ArrayList<URI>();
        Pattern pattern = Pattern.compile(tldJarNamePatterns);
        for (Iterator<Artifact> iter = pluginArtifacts.iterator(); iter.hasNext(); )
        {
            Artifact pluginArtifact = iter.next();
            artifactUris.add(Resource.newResource(pluginArtifact.getFile()).getURI());
        }
        
        PatternMatcher matcher = new PatternMatcher()
        {
            public void matched(URI uri) throws Exception
            {
                //uri of system artifact matches pattern defining list of jars known to contain tlds
                list.add(uri.toURL());
            }
        };
        matcher.match(pattern, artifactUris.toArray(new URI[artifactUris.size()]), false);
        
        return list;
    }
    
    private File getWebXmlFile ()
    throws IOException
    {
        File file = null;
        File baseDir = project.getBasedir().getCanonicalFile();
        File defaultWebAppSrcDir = new File (baseDir, "src/main/webapp").getCanonicalFile();
        File webAppSrcDir = new File (webAppSourceDirectory).getCanonicalFile();
        File defaultWebXml = new File (defaultWebAppSrcDir, "web.xml").getCanonicalFile();
        
        //If the web.xml has been changed from the default, try that
        File webXmlFile = new File (webXml).getCanonicalFile();
        if (webXmlFile.compareTo(defaultWebXml) != 0)
        {
            file = new File (webXml);
            return file;
        }
        
        //If the web app src directory has not been changed from the default, use whatever
        //is set for the web.xml location
        file = new File (webAppSrcDir, "web.xml");
        return file;
    }
}

Back to the top