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.InvocationTargetException;
25  import java.lang.reflect.Method;
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                 if ( StringUtils.isNotEmpty( output ) )
163                 {
164                     // Reconsider to use WARN / ERROR ?
165                     getLog().error( output );
166                 }
167 
168                 StringBuilder msg = new StringBuilder( "\nExit code: " );
169                 msg.append( exitCode );
170                 if ( StringUtils.isNotEmpty( err.getOutput() ) )
171                 {
172                     msg.append( " - " ).append( err.getOutput() );
173                 }
174                 msg.append( '\n' );
175                 msg.append( "Command line was: " ).append( cmd ).append( '\n' ).append( '\n' );
176 
177                 throw new MojoExecutionException( msg.toString() );
178             }
179 
180             if ( StringUtils.isNotEmpty( output ) )
181             {
182                 getLog().info( output );
183             }
184         }
185         catch ( CommandLineException e )
186         {
187             throw new MojoExecutionException( "Unable to execute jlink command: " + e.getMessage(), e );
188         }
189 
190     }
191 
192     protected Toolchain getToolchain()
193     {
194         Toolchain tc = null;
195 
196         if ( jdkToolchain != null )
197         {
198             // Maven 3.3.1 has plugin execution scoped Toolchain Support
199             try
200             {
201                 Method getToolchainsMethod = toolchainManager.getClass().getMethod( "getToolchains", MavenSession.class,
202                                                                                     String.class, Map.class );
203 
204                 @SuppressWarnings( "unchecked" )
205                 List<Toolchain> tcs =
206                     (List<Toolchain>) getToolchainsMethod.invoke( toolchainManager, session, "jdk", jdkToolchain );
207 
208                 if ( tcs != null && tcs.size() > 0 )
209                 {
210                     tc = tcs.get( 0 );
211                 }
212             }
213             catch ( NoSuchMethodException e )
214             {
215                 // ignore
216             }
217             catch ( SecurityException e )
218             {
219                 // ignore
220             }
221             catch ( IllegalAccessException e )
222             {
223                 // ignore
224             }
225             catch ( IllegalArgumentException e )
226             {
227                 // ignore
228             }
229             catch ( InvocationTargetException e )
230             {
231                 // ignore
232             }
233         }
234 
235         if ( tc == null )
236         {
237             // TODO: Check if we should make the type configurable?
238             tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
239         }
240 
241         return tc;
242     }
243 
244     protected MavenProject getProject()
245     {
246         return project;
247     }
248 
249     protected MavenSession getSession()
250     {
251         return session;
252     }
253 
254     /**
255      * Returns the archive file to generate, based on an optional classifier.
256      *
257      * @param basedir the output directory
258      * @param finalName the name of the ear file
259      * @param classifier an optional classifier
260      * @param archiveExt The extension of the file.
261      * @return the file to generate
262      */
263     protected File getArchiveFile( File basedir, String finalName, String classifier, String archiveExt )
264     {
265         if ( basedir == null )
266         {
267             throw new IllegalArgumentException( "basedir is not allowed to be null" );
268         }
269         if ( finalName == null )
270         {
271             throw new IllegalArgumentException( "finalName is not allowed to be null" );
272         }
273         if ( archiveExt == null )
274         {
275             throw new IllegalArgumentException( "archiveExt is not allowed to be null" );
276         }
277 
278         if ( finalName.isEmpty() )
279         {
280             throw new IllegalArgumentException( "finalName is not allowed to be empty." );
281         }
282         if ( archiveExt.isEmpty() )
283         {
284             throw new IllegalArgumentException( "archiveExt is not allowed to be empty." );
285         }
286 
287         StringBuilder fileName = new StringBuilder( finalName );
288 
289         if ( hasClassifier( classifier ) )
290         {
291             fileName.append( "-" ).append( classifier );
292         }
293 
294         fileName.append( '.' );
295         fileName.append( archiveExt );
296 
297         return new File( basedir, fileName.toString() );
298     }
299 
300     protected boolean hasClassifier( String classifier )
301     {
302         boolean result = false;
303         if ( classifier != null && classifier.trim().length() > 0 )
304         {
305             result = true;
306         }
307 
308         return result;
309     }
310 
311     /**
312      * This will convert a module path separated by either {@code :} or {@code ;} into a string which uses the platform
313      * depend path separator uniformly.
314      * 
315      * @param pluginModulePath The module path.
316      * @return The platform separated module path.
317      */
318     protected StringBuilder convertSeparatedModulePathToPlatformSeparatedModulePath( String pluginModulePath )
319     {
320         StringBuilder sb = new StringBuilder();
321         // Split the module path by either ":" or ";" linux/windows path separator and
322         // convert uniformly to the platform used separator.
323         String[] splitModule = pluginModulePath.split( "[;:]" );
324         for ( String module : splitModule )
325         {
326             if ( sb.length() > 0 )
327             {
328                 sb.append( File.pathSeparatorChar );
329             }
330             sb.append( module );
331         }
332         return sb;
333     }
334 
335     /**
336      * Convert a list into a string which is separated by platform depend path separator.
337      * 
338      * @param modulePaths The list of elements.
339      * @return The string which contains the elements separated by {@link File#pathSeparatorChar}.
340      */
341     protected String getPlatformDependSeparateList( List<String> modulePaths )
342     {
343         StringBuilder sb = new StringBuilder();
344         for ( String module : modulePaths )
345         {
346             if ( sb.length() > 0 )
347             {
348                 sb.append( File.pathSeparatorChar );
349             }
350             sb.append( module );
351         }
352         return sb.toString();
353     }
354 
355     /**
356      * Convert a list into a 
357      * @param modules The list of modules.
358      * @return The string with the module list which is separated by {@code ,}.
359      */
360     protected String getCommaSeparatedList( List<String> modules )
361     {
362         StringBuilder sb = new StringBuilder();
363         for ( String module : modules )
364         {
365             if ( sb.length() > 0 )
366             {
367                 sb.append( ',' );
368             }
369             sb.append( module );
370         }
371         return sb.toString();
372     }
373 
374 }