001    package org.apache.myfaces.maven.plugin;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one or more
005     * contributor license agreements.  See the NOTICE file distributed with
006     * this work for additional information regarding copyright ownership.
007     * The ASF licenses this file to You under the Apache License, Version 2.0
008     * (the "License"); you may not use this file except in compliance with
009     * the License.  You may obtain a copy of the License at
010     *
011     *      http://www.apache.org/licenses/LICENSE-2.0
012     *
013     * Unless required by applicable law or agreed to in writing, software
014     * distributed under the License is distributed on an "AS IS" BASIS,
015     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */
019    
020    import java.io.File;
021    import java.io.PrintWriter;
022    import java.io.StringWriter;
023    import java.io.IOException;
024    import java.lang.reflect.Method;
025    import java.net.URL;
026    import java.net.URLClassLoader;
027    import java.util.Collection;
028    import java.util.Iterator;
029    import java.util.List;
030    import java.util.StringTokenizer;
031    import java.util.ArrayList;
032    
033    import org.apache.maven.project.MavenProject;
034    import org.apache.maven.plugin.AbstractMojo;
035    import org.apache.maven.plugin.MojoExecutionException;
036    import org.apache.maven.artifact.DependencyResolutionRequiredException;
037    import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
038    import org.codehaus.plexus.compiler.javac.Commandline;
039    import org.codehaus.plexus.util.cli.DefaultConsumer;
040    import org.codehaus.plexus.util.cli.CommandLineException;
041    import org.codehaus.plexus.util.cli.CommandLineUtils;
042    import org.codehaus.plexus.util.Os;
043    import org.codehaus.plexus.util.StringUtils;
044    import org.codehaus.plexus.util.FileUtils;
045    
046    /**
047     * @author <a href="mailto:jubu@volny.cz">Juraj Burian</a>
048     * @version $Id: AbstractAPTMojo.java 949569 2010-05-30 20:44:27Z bommel $
049     */
050    public abstract class AbstractAPTMojo extends AbstractMojo
051    {
052        /**
053         * PATH_SEPARATOR.
054         */
055        private static final String PATH_SEPARATOR = System.getProperty( "path.separator" );
056        /**
057         * FILE_SEPARATOR.
058         */
059        private static final String FILE_SEPARATOR = System.getProperty( "file.separator" );
060    
061        /**
062         * Integer returned by the Apt compiler to indicate success.
063         */
064        private static final int APT_COMPILER_SUCCESS = 0;
065    
066        /**
067         * class in tools.jar that implements APT
068         */
069        private static final String APT_ENTRY_POINT = "com.sun.tools.apt.Main";
070    
071        /**
072         * method used for apt.
073         */
074        private static final String APT_METHOD_NAME = "process";
075    
076        /**
077         * old method used for apt.
078         */
079        private static final String APT_METHOD_NAME_OLD = "compile";
080    
081        /**
082         * store info about modification of system classpath for Apt compiler
083         */
084        private static boolean isClasspathModified;
085    
086       /**
087        * Working directory when APT compiler is forked
088        * @parameter default-value="${basedir}"
089        * @since 1.0.10
090        */
091        private File workingDir;
092    
093        /**
094         *  A List of targetFiles for SingleSourceTargetMapping
095         *
096         * @parameter
097         */
098        private List targetFiles;
099    
100       /**
101         * enables resource filtering for generated resources
102         *
103         * @parameter    default-value="false"
104         */
105        private boolean resourceFiltering;
106    
107        /**
108         *  targetPath for generated resources
109         *
110         * @parameter
111         */
112        private String resourceTargetPath;
113    
114        /**
115         * Whether to include debugging information in the compiled class files. The
116         * default value is true.
117         * 
118         * @parameter expression="${maven.compiler.debug}" default-value="true"
119         * @readonly
120         */
121        private boolean debug;
122    
123        /**
124         * Comma separated list of "-A" options: Next two examples are equivalent:
125         * 
126         * <pre>
127         *         &lt;A&gt;-Adebug,-Aloglevel=3&lt;/A&gt;
128         * </pre>
129         * <pre>
130         *         &lt;A&gt;debug, loglevel=3&lt;/A&gt;
131         * </pre>
132         *
133         * @parameter alias="A"
134         */
135        private String aptOptions;
136    
137        /**
138         * Output source locations where deprecated APIs are used
139         * 
140         * @parameter
141         */
142        private boolean showDeprecation;
143    
144        /**
145         * Output warnings
146         * 
147         * @parameter
148         */
149        private boolean showWarnings;
150    
151        /**
152         * The -encoding argument for the Apt
153         * 
154         * @parameter
155         */
156        private String encoding;
157    
158        /**
159         * run Apt in verbode mode
160         * 
161         * @parameter expression="${verbose}" default-value="false"
162         */
163        private boolean verbose;
164    
165        /**
166         * The -nocompile argument for the Apt
167         * 
168         * @parameter default-value="false"
169         */
170        private boolean nocompile;
171    
172        /**
173         * The granularity in milliseconds of the last modification date for testing
174         * whether a source needs recompilation
175         * 
176         * @parameter expression="${lastModGranularityMs}" default-value="0"
177         */
178        private int staleMillis;
179    
180        /**
181         * Name of AnnotationProcessorFactory to use; bypasses default discovery
182         * process
183         * 
184         * @parameter
185         */
186        private String factory;
187    
188        /**
189         * Temporary directory that contain the files from the plugin.
190         *
191         * @parameter expression="${project.build.directory}/maven-apt-plugin"
192         * @required
193         * @readonly
194         */
195        private File tempRoot;
196    
197        /**
198         * The directory to run the compiler from if fork is true.
199         * 
200         * @parameter expression="${basedir}"
201         * @required
202         * @readonly
203         */
204        private File basedir;
205    
206        /**
207         * Allows running the compiler in a separate process.
208         * If "false" it uses the built in compiler, while if "true" it will use an executable.
209         *
210         * @parameter default-value="false"
211         */
212        private boolean fork;
213        /**
214         * Force apt call without staleness checking.
215         *
216         * @parameter default-value="false"
217         */
218        private boolean force;
219    
220    
221        /**
222         * The maven project.
223         * 
224         * @parameter expression="${project}"
225         * @required
226         * @readonly
227         */
228        private MavenProject project;
229    
230        /**
231         * The maven project.
232         * @return MavenProject
233         */
234        public MavenProject getProject()
235        {
236            return project;
237        }
238        /**
239         * Force apt call without staleness checking.
240         * @return force
241         */
242        public boolean isForce()
243        {
244            return force;
245        }
246        /**
247         * run Apt in verbode mode
248         * @return verbose
249         */
250        public boolean isVerbose()
251        {
252            return verbose;
253        }
254    
255        /**
256         * The granularity in milliseconds of the last modification date for testing
257         * whether a source needs recompilation
258         * @return staleMillis
259         */
260        public int getStaleMillis()
261        {
262            return staleMillis;
263        }
264       /**
265        *  A List of targetFiles for SingleSourceTargetMapping
266        * @return a List of TargetFiles
267        */
268        protected List getTargetFiles()
269        {
270            return targetFiles;
271        }
272       /**
273        * enables resource filtering for generated resources
274        * @return resourceFiltering
275        */
276        protected boolean isResourceFiltering()
277        {
278            return resourceFiltering;
279        }
280       /**
281        *  targetPath for generated resources
282        * @return resouceTargetPath
283        */
284        protected String getResourceTargetPath()
285        {
286            return resourceTargetPath;
287        }
288        /**
289         *  classpath elements.
290         * @return a List of classPathElements
291         */
292        protected abstract List getClasspathElements();
293        /**
294         * The source directories containing the sources to be compiled.
295         * @return a List of CompileSourceRoots
296         */
297        protected abstract List getCompileSourceRoots();
298    
299        /**
300         * The extra source directories containing the source to be processed.
301         * @return a List of AptSourceRoots
302         */
303        protected abstract List getAptSourceRoots();
304    
305        /**
306         * The directory where compiled classes go.
307         * @return outputDirector
308         */
309        protected abstract File getOutputDirectory();
310    
311       /**
312        * The directory where generated code go.
313        * @return generated
314        */
315        protected abstract String getGenerated();
316    
317      /**
318       *
319       * @return a SourceInclusionScanner
320       */
321        protected abstract SourceInclusionScanner getSourceInclusionScanner();
322    
323      /**
324       * execute
325       * @throws MojoExecutionException
326       */
327        public void execute() throws MojoExecutionException 
328        {
329            getLog().debug( "Using apt compiler" );
330            Commandline cmd = new Commandline();
331            int result = APT_COMPILER_SUCCESS;
332            StringWriter writer = new StringWriter();
333    
334            // Use reflection to be able to build on all JDKs:
335            try
336            {
337                // init comand line
338                setAptCommandlineSwitches( cmd );
339                setAptSpecifics( cmd );
340                setStandards( cmd );
341                setClasspath( cmd );
342                List sourceFiles = new ArrayList();
343                if ( !fillSourcelist( sourceFiles ) )
344                {
345                    if ( getLog().isDebugEnabled() )
346                    {
347                        getLog().debug( "there are not stale sources." );
348                    }
349                    return;
350                }
351                else
352                {
353                    if ( fork )
354                    {
355    
356                         if ( !tempRoot.exists() )
357                         {
358                             tempRoot.mkdirs();
359                         }
360                         File file = new File( tempRoot , "files" );
361                         if ( !getLog().isDebugEnabled() )
362                         {
363                             file.deleteOnExit();
364                         }
365                         try
366                         {
367                             FileUtils.fileWrite( file.getAbsolutePath(),
368                                     StringUtils.join( sourceFiles.iterator(), "\n" ) );
369                             cmd.createArgument().setValue( '@' + file.getPath() );
370                         }
371                         catch ( IOException e )
372                         {
373                             throw new MojoExecutionException( "Unable to write temporary file for command execution", e );
374                         }
375                    }
376                    else
377                    {
378                        Iterator sourceIt = sourceFiles.iterator();
379                        while ( sourceIt.hasNext() )
380                        {
381                            cmdAdd( cmd, (String) sourceIt.next() );
382                        }
383                    }
384                }
385                if ( fork )
386                {
387                    if ( getLog().isDebugEnabled() )
388                    {
389                        getLog().debug( "Working dir: " + workingDir.getAbsolutePath() );
390                    }
391                    cmd.setWorkingDirectory( workingDir.getAbsolutePath() );
392                    cmd.setExecutable( getAptPath() );
393    
394                    if ( getLog().isDebugEnabled() )
395                    {
396                        getLog().debug( "Invoking apt with cmd " + Commandline.toString( cmd.getShellCommandline() ) );
397                    }
398    
399                    CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
400                    try
401                    {
402                        int exitCode = CommandLineUtils.executeCommandLine( cmd, new DefaultConsumer(), err );
403    
404                        getLog().error( err.getOutput() );
405    
406                        if ( exitCode != 0 )
407                        {
408                            throw new MojoExecutionException( "Exit code: " + exitCode + " - " + err.getOutput() );
409                        }
410                     }
411                     catch ( CommandLineException e )
412                     {
413                         throw new MojoExecutionException( "Unable to execute apt command", e );
414                     }
415                }
416                else
417                {
418                    // we need to have tools.jar in lasspath
419                    // due to bug in Apt compiler, system classpath must be modified but in future:
420                    // TODO try separate ClassLoader (see Plexus compiler api)
421                    if ( !isClasspathModified )
422                    {
423                        URL toolsJar = new File( System.getProperty( "java.home" ),
424                            "../lib/tools.jar" ).toURL();
425                        Method m = URLClassLoader.class.getDeclaredMethod( "addURL",
426                              new Class[] { URL.class } );
427                        m.setAccessible( true );
428                        m.invoke( this.getClass().getClassLoader()
429                            .getSystemClassLoader(), new Object[] { toolsJar } );
430                        isClasspathModified = true;
431                    }
432                    Class c = this.getClass().forName( APT_ENTRY_POINT ); // getAptCompilerClass();
433                    Object compiler = c.newInstance();
434                    if ( getLog().isDebugEnabled() )
435                    {
436                        getLog().debug( "Invoking apt with cmd " + cmd.toString() );
437                    }
438                    try
439                    {
440                        Method compile = c.getMethod( APT_METHOD_NAME, new Class[] {
441                            PrintWriter.class, ( new String[] {} ).getClass() } );
442                        result = ( ( Integer ) //
443                        compile.invoke( compiler, new Object[] { new PrintWriter( writer ),
444                              cmd.getArguments() } ) ).intValue();
445                    }
446                    catch ( NoSuchMethodException e )
447                    {
448                      // ignore
449                        Method compile = c.getMethod( APT_METHOD_NAME_OLD, new Class[] {
450                            ( new String[] {} ).getClass(),  PrintWriter.class } );
451                        result = ( ( Integer ) //
452                        compile.invoke( compiler, new Object[] {
453                              cmd.getArguments(),  new PrintWriter( writer ) } ) ).intValue();
454                    }
455                }
456    
457    
458            }
459            catch ( Exception ex )
460            {
461                throw new MojoExecutionException( "Error starting apt compiler", ex );
462            }
463            finally
464            {
465                if ( result != APT_COMPILER_SUCCESS )
466                {
467                    throw new MojoExecutionException( this, "Compilation error.", writer.getBuffer().toString() );
468                }
469                if ( getLog().isDebugEnabled() )
470                {
471                    String r = writer.getBuffer().toString();
472                    if ( 0 != r.length() )
473                    {
474                        getLog().debug( r );
475                    }
476                    getLog().debug( "Apt finished." );
477                }
478            }
479        }
480    
481      /**
482       *
483       * @param cmd
484       */
485        private void setAptCommandlineSwitches( Commandline cmd )
486        {
487            if ( null == aptOptions )
488            {
489                return;
490            }
491            StringTokenizer tokenizer = new StringTokenizer( aptOptions.trim(), "," );
492            while ( tokenizer.hasMoreElements() )
493            {
494                String option = tokenizer.nextToken().trim();
495                if ( !option.startsWith( "-A" ) )
496                {
497                    option = "-A" + option;
498                }
499                cmdAdd( cmd, option );
500            }
501        }
502    
503      /**
504       *
505       * @param cmd
506       * @throws MojoExecutionException
507       */
508        private void setAptSpecifics( Commandline cmd ) throws MojoExecutionException
509        {
510            try
511            {
512                String g = basedir.getAbsolutePath() + FILE_SEPARATOR
513                        + getGenerated();
514                File generatedDir = new File( g );
515                cmdAdd( cmd, "-s", generatedDir.getCanonicalPath() );
516                if ( !generatedDir.exists() )
517                {
518                    generatedDir.mkdirs();
519                }
520            }
521            catch ( Exception e )
522            {
523                throw new MojoExecutionException( //
524                        "Generated directory is invalid.", e );
525            }
526            if ( nocompile )
527            {
528                cmdAdd( cmd, "-nocompile" );
529            }
530            if ( null != factory && 0 != factory.length() )
531            {
532                cmdAdd( cmd, "-factory", factory );
533            }
534        }
535    
536      /**
537       *
538       * @param cmd
539       * @throws MojoExecutionException
540       */
541        private void setStandards( Commandline cmd ) throws MojoExecutionException
542        {
543            if ( debug )
544            {
545                cmdAdd( cmd, "-g" );
546            }
547            if ( !showWarnings )
548            {
549                cmdAdd( cmd, "-nowarn" );
550            }
551            if ( showDeprecation )
552            {
553                cmdAdd( cmd, "-depecation" );
554            }
555            if ( null != encoding )
556            {
557                cmdAdd( cmd, "-encoding", encoding );
558            }
559            if ( verbose )
560            {
561                cmdAdd( cmd, "-verbose" );
562            }
563    
564            // add sourcepath directory
565            setSourcepath(cmd);
566            // add output directory
567            try
568            {
569                if ( !getOutputDirectory().exists() )
570                {
571                    getOutputDirectory().mkdirs();
572                }
573                cmdAdd( cmd, "-d", getOutputDirectory().getCanonicalPath() );
574            }
575            catch ( Exception ex )
576            {
577                throw new MojoExecutionException( //
578                        "Output directory is invalid.", ex );
579            }
580        }
581    
582        private void setSourcepath(Commandline cmd) {
583            StringBuffer buffer = new StringBuffer();
584            for ( Iterator it = getCompileSourceRoots().iterator(); it.hasNext();)
585            {
586                buffer.append( it.next() );
587                if ( it.hasNext() )
588                {
589                    buffer.append( PATH_SEPARATOR );
590                }
591            }
592            if ( getAptSourceRoots() != null)
593            {
594                if (buffer.length() > 0 && getAptSourceRoots().size() > 0)
595                {
596                    buffer.append( PATH_SEPARATOR );
597                }
598                for ( Iterator it = getAptSourceRoots().iterator(); it.hasNext();)
599                {
600                    buffer.append( it.next() );
601                    if ( it.hasNext() )
602                    {
603                        buffer.append( PATH_SEPARATOR );
604                    }
605                }
606            }
607            cmdAdd( cmd, "-sourcepath", buffer.toString() );
608        }
609    
610      /**
611       *
612       * @param cmd
613       * @return
614       * @throws MojoExecutionException
615       */
616        private boolean fillSourcelist( List cmd ) throws MojoExecutionException
617        {
618            boolean has = false;
619            // sources ....
620            Iterator it = getCompileSourceRoots().iterator();
621            if ( getLog().isDebugEnabled() )
622            {
623                getLog().debug( "Checking sourcepath" );
624            }
625            while ( it.hasNext() )
626            {
627                File srcFile = new File( (String) it.next() );
628                has = addIncludedSources( srcFile, cmd, has );
629            }
630            List aptSourcesRoots = getAptSourceRoots();
631            if ( aptSourcesRoots != null )
632            {
633                it = aptSourcesRoots.iterator();
634                while ( it.hasNext() )
635                {
636                    File srcFile = new File( (String) it.next() );
637                    has = addIncludedSources( srcFile, cmd, has );
638                }
639            }
640            return has;
641        }
642    
643      /**
644       *
645       * @param srcFile
646       * @param cmd
647       * @param has
648       * @return
649       * @throws MojoExecutionException
650       */
651        private boolean addIncludedSources( File srcFile, List cmd, boolean has ) throws MojoExecutionException
652        {
653            if ( getLog().isDebugEnabled() )
654            {
655                getLog().debug( "Checking sourcepath in " + srcFile );
656            }
657            if ( srcFile.isDirectory() )
658            {
659                Collection sources = null;
660                try
661                {
662                    sources = //
663                        getSourceInclusionScanner().getIncludedSources( srcFile,
664                            getOutputDirectory() );
665                }
666                catch ( Exception ex )
667                {
668                    throw new MojoExecutionException(
669                        "Can't agregate sources.", ex );
670                }
671                if ( getLog().isDebugEnabled() )
672                {
673                    getLog().debug(
674                            "sources from: " + srcFile.getAbsolutePath() );
675                    String s = "";
676                    for ( Iterator jt = sources.iterator(); jt.hasNext();)
677                    {
678                         s += jt.next() + "\n";
679                    }
680                    getLog().debug( s );
681                }
682                Iterator jt = sources.iterator();
683                while ( jt.hasNext() )
684                {
685                    File src = (File) jt.next();
686                    if ( fork )
687                    {
688                        cmd.add( quotedPathArgument( src.getAbsolutePath() ) );
689                    }
690                    else
691                    {
692                        cmd.add( src.getAbsolutePath() );
693                    }
694                    has = true;
695                }
696            }
697            return has;
698        }
699    
700      /**
701       *
702       * @param cmd
703       * @throws MojoExecutionException
704       * @throws DependencyResolutionRequiredException
705       */
706        private void setClasspath( Commandline cmd ) throws MojoExecutionException, DependencyResolutionRequiredException
707        {
708            StringBuffer buffer = new StringBuffer();
709            for ( Iterator it = getClasspathElements().iterator(); it.hasNext();)
710            {
711                buffer.append( it.next() );
712                if ( it.hasNext() )
713                {
714                    buffer.append( PATH_SEPARATOR );
715                }
716            }
717            cmdAdd( cmd, "-classpath", buffer.toString() );
718        }
719    
720        /**
721         *
722         * @param cmd
723         * @param arg
724         */
725        private void cmdAdd( Commandline cmd, String arg )
726        {
727            /**
728             * OBSOLETE
729             * if( true == getLog().isDebugEnabled() ) { getLog().debug(
730             * arg ); }
731             */
732             cmd.createArgument().setValue( arg );
733            //cmd.add( arg );
734        }
735    
736      /**
737       *
738       * @param cmd
739       * @param arg1
740       * @param arg2
741       */
742        private void cmdAdd( Commandline cmd, String arg1, String arg2 )
743        {
744            /**
745             * OBSOLETE
746             * if( true == getLog().isDebugEnabled() ) { getLog().debug(
747             * arg1 + " " + arg2 ); }
748             */
749            cmdAdd( cmd, arg1 );
750            cmdAdd( cmd, arg2 );
751        }
752    
753      /**
754         * Get the path of apt tool depending the OS.
755         *
756         * @return the path of the apt tool
757         */
758        private String getAptPath()
759        {
760            String aptCommand = "apt";
761            if ( Os.isFamily( "windows" ) )
762            {
763                aptCommand = "apt.exe";
764            }
765    
766            File aptExe;
767    
768            // For IBM's JDK 1.2
769            if ( Os.isName( "aix" ) )
770            {
771                aptExe = new File( System.getProperty( "java.home" ) + "/../sh", aptCommand );
772            }
773            else if ( Os.isFamily( "unix" ) && Os.isFamily( "mac" ) )
774            {
775                aptExe = new File( System.getProperty( "java.home" ) + "/bin", aptCommand );
776            }
777            else
778            {
779                aptExe = new File( System.getProperty( "java.home" ) + "/../bin", aptCommand );
780            }
781    
782            getLog().debug( "Apt executable=[" + aptExe.getAbsolutePath() + "]" );
783    
784            return aptExe.getAbsolutePath();
785        }
786    
787      /**
788       *
789       * @param value
790       * @return quotedPathArgument
791       */
792        private String quotedPathArgument( String value )
793        {
794            String path = value;
795    
796            if ( !StringUtils.isEmpty( path ) )
797            {
798                path = path.replace( '\\', '/' );
799                if ( path.indexOf( "\'" ) != -1 )
800                {
801                    String split[] = path.split( "\'" );
802                    path = "";
803    
804                    for ( int i = 0; i < split.length; i++ )
805                    {
806                        if ( i != split.length - 1 )
807                        {
808                            path = path + split[i] + "\\'";
809                        }
810                        else
811                        {
812                            path = path + split[i];
813                        }
814                    }
815                }
816                path = "'" + path + "'";
817            }
818    
819            return path;
820        }
821    }