View Javadoc
1   package org.apache.maven.plugins.jlink;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.lang.reflect.Method;
25  import java.util.Collection;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Properties;
29  
30  import org.apache.commons.lang3.SystemUtils;
31  import org.apache.maven.execution.MavenSession;
32  import org.apache.maven.plugin.AbstractMojo;
33  import org.apache.maven.plugin.MojoExecutionException;
34  import org.apache.maven.plugins.annotations.Component;
35  import org.apache.maven.plugins.annotations.Parameter;
36  import org.apache.maven.project.MavenProject;
37  import org.apache.maven.toolchain.Toolchain;
38  import org.apache.maven.toolchain.ToolchainManager;
39  import org.codehaus.plexus.util.StringUtils;
40  import org.codehaus.plexus.util.cli.CommandLineException;
41  import org.codehaus.plexus.util.cli.CommandLineUtils;
42  import org.codehaus.plexus.util.cli.Commandline;
43  
44  /**
45   * @author Karl Heinz Marbaise <a href="mailto:khmarbaise@apache.org">khmarbaise@apache.org</a>
46   */
47  public abstract class AbstractJLinkMojo
48      extends AbstractMojo
49  {
50      /**
51       * <p>
52       * Specify the requirements for this jdk toolchain. This overrules the toolchain selected by the
53       * maven-toolchain-plugin.
54       * </p>
55       * <strong>note:</strong> requires at least Maven 3.3.1
56       */
57      @Parameter
58      private Map<String, String> jdkToolchain;
59  
60      @Parameter( defaultValue = "${project}", readonly = true, required = true )
61      private MavenProject project;
62  
63      @Parameter( defaultValue = "${session}", readonly = true, required = true )
64      private MavenSession session;
65  
66      @Component
67      private ToolchainManager toolchainManager;
68  
69      protected String getJLinkExecutable()
70          throws IOException
71      {
72          Toolchain tc = getToolchain();
73  
74          String jLinkExecutable = null;
75          if ( tc != null )
76          {
77              jLinkExecutable = tc.findTool( "jlink" );
78          }
79  
80          // TODO: Check if there exist a more elegant way?
81          String jLinkCommand = "jlink" + ( SystemUtils.IS_OS_WINDOWS ? ".exe" : "" );
82  
83          File jLinkExe;
84  
85          if ( StringUtils.isNotEmpty( jLinkExecutable ) )
86          {
87              jLinkExe = new File( jLinkExecutable );
88  
89              if ( jLinkExe.isDirectory() )
90              {
91                  jLinkExe = new File( jLinkExe, jLinkCommand );
92              }
93  
94              if ( SystemUtils.IS_OS_WINDOWS && jLinkExe.getName().indexOf( '.' ) < 0 )
95              {
96                  jLinkExe = new File( jLinkExe.getPath() + ".exe" );
97              }
98  
99              if ( !jLinkExe.isFile() )
100             {
101                 throw new IOException( "The jlink executable '" + jLinkExe + "' doesn't exist or is not a file." );
102             }
103             return jLinkExe.getAbsolutePath();
104         }
105 
106         // ----------------------------------------------------------------------
107         // Try to find jlink from System.getProperty( "java.home" )
108         // By default, System.getProperty( "java.home" ) = JRE_HOME and JRE_HOME
109         // should be in the JDK_HOME
110         // ----------------------------------------------------------------------
111         jLinkExe = new File( SystemUtils.getJavaHome() + File.separator + ".." + File.separator + "bin", jLinkCommand );
112 
113         // ----------------------------------------------------------------------
114         // Try to find javadocExe from JAVA_HOME environment variable
115         // ----------------------------------------------------------------------
116         if ( !jLinkExe.exists() || !jLinkExe.isFile() )
117         {
118             Properties env = CommandLineUtils.getSystemEnvVars();
119             String javaHome = env.getProperty( "JAVA_HOME" );
120             if ( StringUtils.isEmpty( javaHome ) )
121             {
122                 throw new IOException( "The environment variable JAVA_HOME is not correctly set." );
123             }
124             if ( !new File( javaHome ).getCanonicalFile().exists()
125                 || new File( javaHome ).getCanonicalFile().isFile() )
126             {
127                 throw new IOException( "The environment variable JAVA_HOME=" + javaHome
128                     + " doesn't exist or is not a valid directory." );
129             }
130 
131             jLinkExe = new File( javaHome + File.separator + "bin", jLinkCommand );
132         }
133 
134         if ( !jLinkExe.getCanonicalFile().exists() || !jLinkExe.getCanonicalFile().isFile() )
135         {
136             throw new IOException( "The jlink executable '" + jLinkExe
137                 + "' doesn't exist or is not a file. Verify the JAVA_HOME environment variable." );
138         }
139 
140         return jLinkExe.getAbsolutePath();
141     }
142 
143     protected void executeCommand( Commandline cmd, File outputDirectory )
144         throws MojoExecutionException
145     {
146         if ( getLog().isDebugEnabled() )
147         {
148             // no quoted arguments ???
149             getLog().debug( CommandLineUtils.toString( cmd.getCommandline() ).replaceAll( "'", "" ) );
150         }
151 
152         CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
153         CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
154         try
155         {
156             int exitCode = CommandLineUtils.executeCommandLine( cmd, out, err );
157 
158             String output = ( StringUtils.isEmpty( out.getOutput() ) ? null : '\n' + out.getOutput().trim() );
159 
160             if ( exitCode != 0 )
161             {
162 
163                 if ( StringUtils.isNotEmpty( output ) )
164                 {
165                     // Reconsider to use WARN / ERROR ?
166                    //  getLog().error( output );
167                     for ( String outputLine : output.split( "\n" ) )
168                     {
169                         getLog().error( outputLine );
170                     }
171                 }
172 
173                 StringBuilder msg = new StringBuilder( "\nExit code: " );
174                 msg.append( exitCode );
175                 if ( StringUtils.isNotEmpty( err.getOutput() ) )
176                 {
177                     msg.append( " - " ).append( err.getOutput() );
178                 }
179                 msg.append( '\n' );
180                 msg.append( "Command line was: " ).append( cmd ).append( '\n' ).append( '\n' );
181 
182                 throw new MojoExecutionException( msg.toString() );
183             }
184 
185             if ( StringUtils.isNotEmpty( output ) )
186             {
187                 //getLog().info( output );
188                 for ( String outputLine : output.split( "\n" ) )
189                 {
190                     getLog().info( outputLine );
191                 }
192             }
193         }
194         catch ( CommandLineException e )
195         {
196             throw new MojoExecutionException( "Unable to execute jlink command: " + e.getMessage(), e );
197         }
198 
199     }
200 
201     protected Toolchain getToolchain()
202     {
203         Toolchain tc = null;
204 
205         if ( jdkToolchain != null )
206         {
207             // Maven 3.3.1 has plugin execution scoped Toolchain Support
208             try
209             {
210                 Method getToolchainsMethod = toolchainManager.getClass().getMethod( "getToolchains", MavenSession.class,
211                                                                                     String.class, Map.class );
212 
213                 @SuppressWarnings( "unchecked" )
214                 List<Toolchain> tcs =
215                     (List<Toolchain>) getToolchainsMethod.invoke( toolchainManager, session, "jdk", jdkToolchain );
216 
217                 if ( tcs != null && tcs.size() > 0 )
218                 {
219                     tc = tcs.get( 0 );
220                 }
221             }
222             catch ( ReflectiveOperationException e )
223             {
224                 // ignore
225             }
226             catch ( SecurityException e )
227             {
228                 // ignore
229             }
230             catch ( IllegalArgumentException e )
231             {
232                 // ignore
233             }
234         }
235 
236         if ( tc == null )
237         {
238             // TODO: Check if we should make the type configurable?
239             tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
240         }
241 
242         return tc;
243     }
244 
245     protected MavenProject getProject()
246     {
247         return project;
248     }
249 
250     protected MavenSession getSession()
251     {
252         return session;
253     }
254 
255     /**
256      * Returns the archive file to generate, based on an optional classifier.
257      *
258      * @param basedir the output directory
259      * @param finalName the name of the ear file
260      * @param classifier an optional classifier
261      * @param archiveExt The extension of the file.
262      * @return the file to generate
263      */
264     protected File getArchiveFile( File basedir, String finalName, String classifier, String archiveExt )
265     {
266         if ( basedir == null )
267         {
268             throw new IllegalArgumentException( "basedir is not allowed to be null" );
269         }
270         if ( finalName == null )
271         {
272             throw new IllegalArgumentException( "finalName is not allowed to be null" );
273         }
274         if ( archiveExt == null )
275         {
276             throw new IllegalArgumentException( "archiveExt is not allowed to be null" );
277         }
278 
279         if ( finalName.isEmpty() )
280         {
281             throw new IllegalArgumentException( "finalName is not allowed to be empty." );
282         }
283         if ( archiveExt.isEmpty() )
284         {
285             throw new IllegalArgumentException( "archiveExt is not allowed to be empty." );
286         }
287 
288         StringBuilder fileName = new StringBuilder( finalName );
289 
290         if ( hasClassifier( classifier ) )
291         {
292             fileName.append( "-" ).append( classifier );
293         }
294 
295         fileName.append( '.' );
296         fileName.append( archiveExt );
297 
298         return new File( basedir, fileName.toString() );
299     }
300 
301     protected boolean hasClassifier( String classifier )
302     {
303         boolean result = false;
304         if ( classifier != null && classifier.trim().length() > 0 )
305         {
306             result = true;
307         }
308 
309         return result;
310     }
311 
312     /**
313      * This will convert a module path separated by either {@code :} or {@code ;} into a string which uses the platform
314      * depend path separator uniformly.
315      * 
316      * @param pluginModulePath The module path.
317      * @return The platform separated module path.
318      */
319     protected StringBuilder convertSeparatedModulePathToPlatformSeparatedModulePath( String pluginModulePath )
320     {
321         StringBuilder sb = new StringBuilder();
322         // Split the module path by either ":" or ";" linux/windows path separator and
323         // convert uniformly to the platform used separator.
324         String[] splitModule = pluginModulePath.split( "[;:]" );
325         for ( String module : splitModule )
326         {
327             if ( sb.length() > 0 )
328             {
329                 sb.append( File.pathSeparatorChar );
330             }
331             sb.append( module );
332         }
333         return sb;
334     }
335 
336     /**
337      * Convert a list into a string which is separated by platform depend path separator.
338      * 
339      * @param modulePaths The list of elements.
340      * @return The string which contains the elements separated by {@link File#pathSeparatorChar}.
341      */
342     protected String getPlatformDependSeparateList( Collection<String> modulePaths )
343     {
344         StringBuilder sb = new StringBuilder();
345         for ( String module : modulePaths )
346         {
347             if ( sb.length() > 0 )
348             {
349                 sb.append( File.pathSeparatorChar );
350             }
351             sb.append( module );
352         }
353         return sb.toString();
354     }
355 
356     /**
357      * Convert a list into a 
358      * @param modules The list of modules.
359      * @return The string with the module list which is separated by {@code ,}.
360      */
361     protected String getCommaSeparatedList( Collection<String> modules )
362     {
363         StringBuilder sb = new StringBuilder();
364         for ( String module : modules )
365         {
366             if ( sb.length() > 0 )
367             {
368                 sb.append( ',' );
369             }
370             sb.append( module );
371         }
372         return sb.toString();
373     }
374 
375 }