View Javadoc
1   package org.codehaus.mojo.rmic;
2   
3   /*
4    * Copyright (c) 2004-2017, Codehaus.org
5    *
6    * Permission is hereby granted, free of charge, to any person obtaining a copy of
7    * this software and associated documentation files (the "Software"), to deal in
8    * the Software without restriction, including without limitation the rights to
9    * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10   * of the Software, and to permit persons to whom the Software is furnished to do
11   * so, subject to the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be included in all
14   * copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22   * SOFTWARE.
23   */
24  
25  import org.apache.maven.plugin.AbstractMojo;
26  import org.apache.maven.plugin.MojoExecutionException;
27  import org.apache.maven.plugins.annotations.Parameter;
28  import org.apache.maven.project.MavenProject;
29  import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
30  import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
31  import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
32  import org.codehaus.plexus.util.StringUtils;
33  
34  import java.io.File;
35  import java.net.URI;
36  import java.net.URL;
37  import java.net.URLClassLoader;
38  import java.util.ArrayList;
39  import java.util.Collection;
40  import java.util.Collections;
41  import java.util.HashSet;
42  import java.util.List;
43  import java.util.Set;
44  
45  /**
46   * Generic super class of rmi compiler mojos.
47   *
48   * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
49   * @version $Id$
50   */
51  public abstract class AbstractRmiMojo
52          extends AbstractMojo
53  {
54      private static final String STUB_CLASS_PATTERN = "**/*_Stub.class";
55      // ----------------------------------------------------------------------
56      // Configurable parameters
57      // ----------------------------------------------------------------------
58  
59      private Source source;
60      
61      /**
62       * A <code>List</code> of <code>Source</code> configurations to compile.
63       */
64      @Parameter
65      private List<Source> sources;
66  
67      /**
68       * A list of inclusions when searching for classes to compile.
69       */
70      @Parameter
71      @SuppressWarnings( "unused" )
72      protected Set<String> includes;
73  
74      /**
75       * A list of exclusions when searching for classes to compile.
76       */
77      @Parameter
78      @SuppressWarnings( "unused" )
79      protected Set<String> excludes;
80  
81      /**
82       * The id of the rmi compiler to use.
83       */
84      @Parameter( defaultValue = "sun" )
85      protected String compiler;
86  
87      private RmiCompiler rmiCompiler = new BuiltInRmiCompiler();
88  
89      /**
90       * The version of the rmi protocol to which the stubs should be compiled. Valid values include 1.1, 1.2, compat. See
91       * the rmic documentation for more information. If nothing is specified the underlying rmi compiler will
92       * choose the default value.  For example, in sun jdk 1.5 the default is 1.2.
93       */
94      @SuppressWarnings( "unused" )
95      @Parameter
96      private String version;
97  
98      /**
99       * Create stubs for IIOP.
100      */
101     @SuppressWarnings( "unused" )
102     @Parameter( defaultValue = "false" )
103     private boolean iiop;
104 
105     /**
106      * Do not create stubs optimized for same process.
107      */
108     @SuppressWarnings( "unused" )
109     @Parameter( defaultValue = "false" )
110     private boolean noLocalStubs;
111 
112     /**
113      * Create IDL.
114      */
115     @SuppressWarnings( "unused" )
116     @Parameter( defaultValue = "false" )
117     private boolean idl;
118 
119     /**
120      * Do not generate methods for valuetypes.
121      */
122     @SuppressWarnings( "unused" )
123     @Parameter( defaultValue = "false" )
124     private boolean noValueMethods;
125 
126     /**
127      * Do not delete intermediate generated source files.
128      */
129     @SuppressWarnings( "unused" )
130     @Parameter( defaultValue = "false" )
131     private boolean keep;
132 
133     /**
134      * Turn off rmic warnings.
135      */
136     @SuppressWarnings( "unused" )
137     @Parameter( defaultValue = "false" )
138     private boolean nowarn;
139 
140     /**
141      * Enable poa generation.
142      */
143     @SuppressWarnings( "unused" )
144     @Parameter( defaultValue = "false" )
145     private boolean poa;
146 
147     /**
148      * Enable verbose output.
149      */
150     @SuppressWarnings( "unused" )
151     @Parameter( defaultValue = "false" )
152     private boolean verbose;
153 
154     /**
155      * Time in milliseconds between automatic recompilations. A value of 0 means that up to date rmic output classes
156      * will not be recompiled until the source classes change.
157      */
158     @SuppressWarnings( "unused" )
159     @Parameter( defaultValue = "0" )
160     private int staleMillis;
161 
162     // ----------------------------------------------------------------------
163     // Constant parameters
164     // ----------------------------------------------------------------------
165 
166     /**
167      * The maven project.
168      */
169     @Parameter( defaultValue = "${project}", readonly = true )
170     protected MavenProject project;
171 
172     /**
173      * The interface between ths class and the rest of the world - unit tests replace the default implementation.
174      */
175     private DependenciesFacade dependencies = new DependenciesFacadeImpl();
176 
177     /**
178      * The default implementation of the dependencies.
179      */
180     private static final DependenciesFacade DEPENDENCIES_FACADE = new DependenciesFacadeImpl();
181 
182     /**
183      * Creates the abstract class using a production implementation of the dependencies.
184      */
185     AbstractRmiMojo()
186     {
187         this( DEPENDENCIES_FACADE );
188     }
189 
190 
191     /**
192      * Creates the abstract class using the specified implementation of the dependencies.
193      *
194      * @param dependencies the implementation of the dependencies to use.
195      */
196     AbstractRmiMojo( DependenciesFacade dependencies )
197     {
198         this.dependencies = dependencies;
199     }
200 
201     /**
202      * Get the list of sub-configurations.
203      */
204     List<Source> getSources()
205     {
206         return sources;
207     }
208 
209     /**
210      * Get the list of elements to add to the classpath of rmic
211      *
212      * @return list of classpath elements
213      */
214     public abstract List<String> getProjectClasspathElements();
215 
216     /**
217      * Get the directory where rmic generated class files are written.
218      *
219      * @return the directory
220      */
221     public abstract File getOutputDirectory();
222 
223     /**
224      * Get the directory where Remote impl classes are located.
225      *
226      * @return path to compiled classes
227      */
228     public abstract File getClassesDirectory();
229 
230     /**
231      * Main mojo execution.
232      *
233      * @throws MojoExecutionException if there is a problem executing the mojo.
234      */
235     public void execute() throws MojoExecutionException
236     {
237         if ( sources != null && source != null && source.getConfiguredOptions().length() > 0 )
238         {
239             throw new MojoExecutionException( "May not use <source> elements in addition to switches "
240                     + "without a <source> element: " + source.getConfiguredOptions() );
241         }
242         if ( sources == null || sources.isEmpty() )
243         {
244             sources = Collections.singletonList( getSource() );
245         }
246 
247         for ( Source source : sources )
248         {
249             doExecute( source );
250         }
251     }
252 
253     private void doExecute( Source source ) throws MojoExecutionException
254     {
255         rmiCompiler.setLog( getLog() );
256 
257         if ( source.isVerbose() )
258         {
259             getLog().debug( source.toString() );
260         }
261 
262 
263         if ( !getOutputDirectory().isDirectory() )
264         {
265             if ( !getOutputDirectory().mkdirs() )
266             {
267                 throw new MojoExecutionException( "Could not make output directory: " + "'"
268                         + getOutputDirectory().getAbsolutePath() + "'." );
269             }
270         }
271 
272         try
273         {
274             // Get the list of classes to compile
275             Set<File> remoteClassesToCompile = getRemoteClasses( source );
276 
277             if ( remoteClassesToCompile.size() == 0 )
278             {
279                 getLog().info( "No out of date rmi classes to process." );
280                 return;
281             }
282 
283             getLog().info( "Compiling " + remoteClassesToCompile.size() + " remote classes" );
284             
285             RmiCompilerConfiguration config = new RmiCompilerConfiguration();
286             config.setClasspathEntries( getRmicClasspathElements() );
287             config.addSourceLocation( getClassesDirectory().getPath() );
288             config.setSourceFiles( remoteClassesToCompile );
289             config.setIdl( source.isIdl() );
290             config.setIiop( source.isIiop() );
291             config.setKeep( source.isKeep() );
292             config.setNoLocalStubs( source.isNoLocalStubs() );
293             config.setNoValueMethods( source.isNoValueMethods() );
294             config.setNowarn( source.isNowarn() );
295             config.setOutputLocation( getOutputDirectory().getAbsolutePath() );
296             config.setPoa( source.isPoa() );
297             config.setVerbose( source.isVerbose() );
298             config.setVersion( source.getVersion() );
299             
300             rmiCompiler.execute( config );
301         }
302         catch ( RmiCompilerException e )
303         {
304             throw new MojoExecutionException( "Error while executing the RMI compiler.", e );
305         }
306     }
307     
308 
309     /**
310      * Get the list of elements to add to the classpath of rmic
311      *
312      * @return list of classpath elements
313      */
314     private List<String> getRmicClasspathElements()
315     {
316         List<String> classpathElements = getProjectClasspathElements();
317 
318         if ( !classpathElements.contains( getClassesDirectory().getAbsolutePath() ) )
319         {
320             classpathElements.add( getClassesDirectory().getAbsolutePath() );
321         }
322 
323         return classpathElements;
324     }
325 
326     /**
327      * Search the input directory for classes to compile.
328      *
329      * @param source the source element on which to operate
330      * @return a list of class names to rmic
331      */
332     private Set<File> getRemoteClasses( Source source )
333     {
334         Set<File> remoteClasses = new HashSet<>();
335 
336         try
337         {
338             // Set up the classloader
339             List<URL> classpathList = generateUrlCompileClasspath();
340             URL[] classpathUrls = new URL[classpathList.size()];
341             classpathUrls[0] = getClassesDirectory().toURI().toURL();
342             classpathUrls = classpathList.toArray( classpathUrls );
343             dependencies.defineUrlClassLoader( classpathUrls );
344 
345             // Scan for remote classes
346 
347             SourceInclusionScanner scanner = createScanner( source.getIncludes(), getExcludes( source ) );
348             scanner.addSourceMapping( new SuffixMapping( ".class", "_Stub.class" ) );
349             
350             Collection<File> staleRemoteClasses
351                     = scanner.getIncludedSources( getClassesDirectory(), getOutputDirectory() );
352 
353             for ( File file : staleRemoteClasses )
354             {
355                 URI relativeURI = getClassesDirectory().toURI().relativize( file.toURI() );
356                 String className = fileToClassName( relativeURI.toString() );
357                 Class<?> candidateClass = dependencies.loadClass( className );
358                 if ( isRemoteRmiClass( candidateClass, source.isIiop() ) )
359                 {
360                     // file is absolute, we need relative files
361                     remoteClasses.add( new File( relativeURI.toString() ) );
362                 }
363             }
364 
365             // Check for classes in a classpath jar
366             for ( String include : source.getIncludes() )
367             {
368                 File includeFile = new File( getClassesDirectory(), include );
369                 if ( ( include.contains( "*" ) ) || dependencies.fileExists( includeFile ) )
370                 {
371                     continue;
372                 }
373                 // We have found a class that is not in the classes dir.
374                 remoteClasses.add( includeFile );
375             }
376         }
377         catch ( Exception e )
378         {
379             getLog().warn( "Problem while scanning for classes: " + e );
380         }
381         return remoteClasses;
382     }
383 
384     private SourceInclusionScanner createScanner( Set<String> includes, Set<String> excludes )
385     {
386         return dependencies.createScanner( staleMillis, includes, excludes );
387     }
388 
389     private Set<String> getExcludes( Source source )
390     {
391         Set<String> excludes = source.getExcludes();
392         excludes.add( STUB_CLASS_PATTERN );
393         return excludes;
394     }
395 
396     private static String fileToClassName( String classFileName )
397     {
398         return StringUtils.replace( StringUtils.replace( classFileName, ".class", "" ), "/", "." );
399     }
400 
401     // Check that each class implements java.rmi.Remote, ignore interfaces unless in IIOP mode
402     private boolean isRemoteRmiClass( Class<?> remoteClass, boolean isIiop )
403     {
404         return java.rmi.Remote.class.isAssignableFrom( remoteClass ) && ( !remoteClass.isInterface() || isIiop );
405     }
406 
407     /**
408      * Returns a list of URL objects that represent the classpath elements. This is useful for using a URLClassLoader
409      *
410      * @return list of url classpath elements
411      */
412     private List<URL> generateUrlCompileClasspath()
413             throws MojoExecutionException
414     {
415         List<URL> rmiCompileClasspath = new ArrayList<>();
416         try
417         {
418             rmiCompileClasspath.add( getClassesDirectory().toURI().toURL() );
419             for ( String classpathElement : getRmicClasspathElements() )
420             {
421                 URL pathUrl = new File( classpathElement ).toURI().toURL();
422                 rmiCompileClasspath.add( pathUrl );
423             }
424         }
425         catch ( Exception e )
426         {
427             e.printStackTrace();
428             throw new MojoExecutionException( "Problem while generating classpath: " + e.getMessage() );
429         }
430         return rmiCompileClasspath;
431     }
432 
433     private Source getSource()
434     {
435         if ( source == null )
436         {
437             source = new Source();
438         }
439         return source;
440     }
441     
442     // Plexus trick: if there's a setter for a @parameter, use the setter
443 
444     /**
445      * Specifies files to include in rmic processing
446      * @param includes a set of file name patterns
447      */
448     @SuppressWarnings( "unused" )
449     public void setIncludes( Set<String> includes )
450     {
451         getSource().setIncludes( includes );
452     }
453 
454     /**
455      * Specifies files to exclude from rmic processing
456      * @param excludes a set of file name patterns
457      */
458     public void setExcludes( Set<String> excludes )
459     {
460         getSource().setExcludes( excludes );
461     }
462 
463     /**
464      * Specifies a version to be passed to the compiler. Defaults to not passing one.
465      * @param version the version to pass
466      */
467     public void setVersion( String version )
468     {
469         getSource().setVersion( version );
470     }
471 
472     /**
473      * Specifies whether the '-iiop' option will be passed to the compiler.
474      * @param iiop if true, will pass the option
475      */
476     public void setIiop( Boolean iiop )
477     {
478         getSource().setIiop( iiop );
479     }
480 
481     /**
482      * Specifies whether the '-nolocalstubs' option will be passed to the compiler. Only applicable if the '-iiop'
483      * option is also set.
484      * @param noLocalStubs if true, will pass the option
485      */
486     public void setNoLocalStubs( Boolean noLocalStubs )
487     {
488         getSource().setNoLocalStubs( noLocalStubs );
489     }
490 
491     /**
492      * Specifies whether the '-idl' option will be passed to the compiler.
493      * @param idl if true, will pass the option
494      */
495     @SuppressWarnings( "unused" )
496     public void setIdl( Boolean idl )
497     {
498         getSource().setIdl( idl );
499     }
500 
501     /**
502      * Specifies whether the '-noValueMethods' option will be passed to the compiler. Only applicable if the '-idl'
503      * option is also set.
504      * @param noValueMethods if true, will pass the option
505      */
506     @SuppressWarnings( "unused" )
507     public void setNoValueMethods( Boolean noValueMethods )
508     {
509         getSource().setNoValueMethods( noValueMethods );
510     }
511 
512     /**
513      * Specifies whether the '-keep' option will be passed to the compiler.
514      * @param keep if true, will pass the option
515      */
516     @SuppressWarnings( "unused" )
517     public void setKeep( Boolean keep )
518     {
519         getSource().setKeep( keep );
520     }
521 
522     /**
523      * Specifies whether the '-warn' option will be passed to the compiler.
524      * @param nowarn if true, will pass the option
525      */
526     @SuppressWarnings( "unused" )
527     public void setNowarn( Boolean nowarn )
528     {
529         getSource().setNowarn( nowarn );
530     }
531 
532     /**
533      * Specifies whether the '-poa' option will be passed to the compiler. Only applicable if the '-iiop' option
534      * is also set.
535      * @param poa if true, will pass the option
536      */
537     public void setPoa( Boolean poa )
538     {
539         getSource().setPoa( poa );
540     }
541 
542     /**
543      * Specifies whether the '-verbose' option will be passed to the compiler.
544      * @param verbose if true, will pass the option
545      */
546     public void setVerbose( Boolean verbose )
547     {
548         getSource().setVerbose( verbose );
549     }
550     
551     
552     /**
553      * An interface for dependencies on the file system and related mojo base classes.
554      */
555     interface DependenciesFacade
556     {
557         boolean fileExists( File includeFile );
558 
559         SourceInclusionScanner createScanner( int staleMillis, Set<String> includes, Set<String> excludes );
560 
561         Class<?> loadClass( String className ) throws ClassNotFoundException;
562 
563         void defineUrlClassLoader( URL[] classpathUrls );
564     }
565 
566     /**
567      * Standard file system and mojo dependencies.
568      */
569     private static class DependenciesFacadeImpl implements DependenciesFacade
570     {
571         private static URLClassLoader loader;
572 
573         public Class<?> loadClass( String className ) throws ClassNotFoundException
574         {
575             return loader.loadClass( className );
576         }
577 
578         public void defineUrlClassLoader( URL[] classpathUrls )
579         {
580             loader = new URLClassLoader( classpathUrls );
581         }
582 
583         public boolean fileExists( File includeFile )
584         {
585             return includeFile.exists();
586         }
587 
588         public SourceInclusionScanner createScanner( int staleMillis, Set<String> includes, Set<String> excludes )
589         {
590             return new StaleSourceScanner( staleMillis, includes, excludes );
591         }
592     }
593 }