View Javadoc
1   package org.apache.maven.plugins.assembly.utils;
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.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.List;
26  import java.util.ListIterator;
27  import java.util.Properties;
28  
29  import org.apache.maven.artifact.Artifact;
30  import org.apache.maven.execution.MavenSession;
31  import org.apache.maven.plugins.assembly.AssemblerConfigurationSource;
32  import org.apache.maven.plugins.assembly.format.AssemblyFormattingException;
33  import org.apache.maven.plugins.assembly.model.Assembly;
34  import org.apache.maven.project.MavenProject;
35  import org.codehaus.plexus.interpolation.fixed.FixedStringSearchInterpolator;
36  import org.codehaus.plexus.interpolation.fixed.MapBasedValueSource;
37  import org.codehaus.plexus.interpolation.fixed.PrefixedObjectValueSource;
38  import org.codehaus.plexus.interpolation.fixed.PrefixedPropertiesValueSource;
39  import org.codehaus.plexus.interpolation.fixed.PropertiesBasedValueSource;
40  import org.codehaus.plexus.util.Os;
41  import org.codehaus.plexus.util.StringUtils;
42  import org.slf4j.Logger;
43  
44  /**
45   *
46   */
47  public final class AssemblyFormatUtils
48  {
49  
50      private AssemblyFormatUtils()
51      {
52      }
53  
54      /**
55       * Get the full name of the distribution artifact
56       *
57       * @param assembly the assembly
58       * @return the distribution name
59       */
60      public static String getDistributionName( final Assembly assembly, final AssemblerConfigurationSource configSource )
61      {
62          final String finalName = configSource.getFinalName();
63          final boolean appendAssemblyId = configSource.isAssemblyIdAppended();
64  
65          String distributionName = finalName;
66          if ( appendAssemblyId )
67          {
68              distributionName = finalName + "-" + assembly.getId();
69          }
70  
71          return distributionName;
72      }
73  
74  
75      public static FixedStringSearchInterpolator finalNameInterpolator( String finalName )
76      {
77          final Properties specialExpressionOverrides = new Properties();
78  
79          if ( finalName != null )
80          {
81              specialExpressionOverrides.setProperty( "finalName", finalName );
82              specialExpressionOverrides.setProperty( "build.finalName", finalName );
83          }
84          else
85          {
86              return FixedStringSearchInterpolator.empty();
87          }
88  
89          return FixedStringSearchInterpolator.create( new PropertiesBasedValueSource( specialExpressionOverrides ) );
90      }
91  
92      public static FixedStringSearchInterpolator moduleProjectInterpolator( final MavenProject moduleProject )
93      {
94          if ( moduleProject != null )
95          {
96              return FixedStringSearchInterpolator.createWithPermittedNulls(
97                  new PrefixedObjectValueSource( "module.", moduleProject ),
98                  new PrefixedPropertiesValueSource( "module.properties.", moduleProject.getProperties() ),
99                  moduleProject.getArtifact() != null
100                     ? new PrefixedObjectValueSource( "module.", moduleProject.getArtifact() )
101                     : null );
102         }
103         else
104         {
105             return FixedStringSearchInterpolator.empty();
106         }
107 
108     }
109 
110     public static FixedStringSearchInterpolator moduleArtifactInterpolator( Artifact moduleArtifact )
111     {
112         if ( moduleArtifact != null )
113         {
114             final String groupIdPath = moduleArtifact.getGroupId().replace( '.', '/' );
115 
116             return FixedStringSearchInterpolator.create(
117                      new MapBasedValueSource( Collections.singletonMap( "module.groupIdPath", groupIdPath ) ),
118                      new PrefixedObjectValueSource( "module.", moduleArtifact ),
119                      new PrefixedObjectValueSource( "module.", moduleArtifact.getArtifactHandler() ),
120                      new PrefixedObjectValueSource( "module.handler.", moduleArtifact.getArtifactHandler() ) );
121         }
122         else
123         {
124             return FixedStringSearchInterpolator.empty();
125         }
126 
127     }
128 
129     public static FixedStringSearchInterpolator artifactProjectInterpolator( final MavenProject artifactProject )
130     {
131         if ( artifactProject != null )
132         {
133             PrefixedObjectValueSource vs = null;
134             if ( artifactProject.getArtifact() != null )
135             {
136                 vs = new PrefixedObjectValueSource( "artifact.", artifactProject.getArtifact() );
137             }
138 
139             final String groupIdPath = artifactProject.getGroupId().replace( '.', '/' );
140 
141             return FixedStringSearchInterpolator.createWithPermittedNulls(
142                 new MapBasedValueSource( Collections.singletonMap( "artifact.groupIdPath", groupIdPath ) ),
143                 new PrefixedObjectValueSource( "artifact.", artifactProject ),
144                 new PrefixedPropertiesValueSource( "artifact.properties.", artifactProject.getProperties() ), vs );
145         }
146         else
147         {
148             return FixedStringSearchInterpolator.empty();
149         }
150     }
151 
152     public static FixedStringSearchInterpolator artifactInterpolator( final Artifact artifact )
153     {
154         final String groupIdPath = artifact.getGroupId().replace( '.', '/' );
155 
156         return FixedStringSearchInterpolator.create( 
157                          new MapBasedValueSource( Collections.singletonMap( "artifact.groupIdPath", groupIdPath ) ),
158                          new PrefixedObjectValueSource( "artifact.", artifact ),
159                          new PrefixedObjectValueSource( "artifact.", artifact.getArtifactHandler() ),
160                          new PrefixedObjectValueSource( "artifact.handler.", artifact.getArtifactHandler() ) );
161     }
162 
163 
164     public static FixedStringSearchInterpolator classifierRules( final Artifact artifact )
165     {
166         final Properties specialRules = new Properties();
167 
168         final String classifier = ProjectUtils.getClassifier( artifact );
169         if ( classifier != null )
170         {
171             specialRules.setProperty( "dashClassifier?", "-" + classifier );
172             specialRules.setProperty( "dashClassifier", "-" + classifier );
173         }
174         else
175         {
176             specialRules.setProperty( "dashClassifier?", "" );
177             specialRules.setProperty( "dashClassifier", "" );
178         }
179 
180         return FixedStringSearchInterpolator.create( new PropertiesBasedValueSource( specialRules ) );
181     }
182 
183 
184     /**
185      * ORDER OF INTERPOLATION PRECEDENCE:
186      * <ol>
187      * <li>Support for special expressions, like ${finalName} (use the assembly plugin configuration not the build
188      * config)</li>
189      * <li>prefixed with "module." if moduleProject is non-null
190      * <ol>
191      * <li>MavenProject instance for module being assembled</li>
192      * </ol>
193      * </li>
194      * <li>prefixed with "artifact." if artifactProject is non-null
195      * <ol>
196      * <li>MavenProject instance for artifact</li>
197      * </ol>
198      * </li>
199      * <li>user-defined properties from the command line</li>
200      * <li>prefixed with "pom." or "project.", or no prefix at all
201      * <ol>
202      * <li>MavenProject instance from current build</li>
203      * </ol>
204      * </li>
205      * <li>properties from main project</li>
206      * <li>system properties, from the MavenSession instance (to support IDEs)</li>
207      * <li>environment variables.</li>
208      * </ol>
209      */
210     public static String getOutputDirectory( final String output, final MavenProject artifactProject,
211                                              final String finalName, final AssemblerConfigurationSource configSource )
212         throws AssemblyFormattingException
213     {
214         return getOutputDirectory( output, finalName, configSource, moduleProjectInterpolator( null ),
215                                    artifactProjectInterpolator( artifactProject ) );
216     }
217 
218 
219     private static FixedStringSearchInterpolator executionPropertiesInterpolator(
220         AssemblerConfigurationSource configSource )
221     {
222         MavenSession session;
223 
224         if ( configSource != null )
225         {
226             session = configSource.getMavenSession();
227 
228             if ( session != null )
229             {
230                 Properties userProperties = session.getExecutionProperties(); // this is added twice....
231 
232                 if ( userProperties != null )
233                 {
234                     return FixedStringSearchInterpolator.create( new PropertiesBasedValueSource( userProperties ) );
235                 }
236             }
237         }
238         return FixedStringSearchInterpolator.empty();
239     }
240 
241     private static FixedStringSearchInterpolator mainProjectOnlyInterpolator( MavenProject mainProject )
242     {
243         if ( mainProject != null )
244         {
245             // 5
246             return FixedStringSearchInterpolator.create(
247                 new org.codehaus.plexus.interpolation.fixed.PrefixedObjectValueSource(
248                     InterpolationConstants.PROJECT_PREFIXES, mainProject, true ) );
249         }
250         else
251         {
252             return FixedStringSearchInterpolator.empty();
253         }
254     }
255 
256 
257     /**
258      * ORDER OF INTERPOLATION PRECEDENCE:
259      * <ol>
260      * <li>prefixed with "module.", if moduleProject != null
261      * <ol>
262      * <li>Artifact instance for module, if moduleArtifact != null</li>
263      * <li>ArtifactHandler instance for module, if moduleArtifact != null</li>
264      * <li>MavenProject instance for module</li>
265      * </ol>
266      * </li>
267      * <li>prefixed with "artifact."
268      * <ol>
269      * <li>Artifact instance</li>
270      * <li>ArtifactHandler instance for artifact</li>
271      * <li>MavenProject instance for artifact</li>
272      * </ol>
273      * </li>
274      * <li>prefixed with "pom." or "project."
275      * <ol>
276      * <li>MavenProject instance from current build</li>
277      * </ol>
278      * </li>
279      * <li>no prefix, using main project instance
280      * <ol>
281      * <li>MavenProject instance from current build</li>
282      * </ol>
283      * </li>
284      * <li>Support for special expressions, like ${dashClassifier?}</li>
285      * <li>user-defined properties from the command line</li>
286      * <li>properties from main project</li>
287      * <li>system properties, from the MavenSession instance (to support IDEs)</li>
288      * <li>environment variables.</li>
289      * </ol>
290      */
291 
292 
293     public static String fixRelativeRefs( String src )
294     {
295         String value = src;
296 
297         String[] separators = { "/", "\\" };
298 
299         String finalSep = null;
300         for ( String sep : separators )
301         {
302             if ( value.endsWith( sep ) )
303             {
304                 finalSep = sep;
305             }
306 
307             if ( value.contains( "." + sep ) )
308             {
309                 List<String> parts = new ArrayList<>();
310                 parts.addAll( Arrays.asList( value.split( sep.replace( "\\", "\\\\" ) ) ) );
311 
312                 for ( ListIterator<String> it = parts.listIterator(); it.hasNext(); )
313                 {
314                     String part = it.next();
315                     if ( ".".equals( part ) )
316                     {
317                         it.remove();
318                     }
319                     else if ( "..".equals( part ) )
320                     {
321                         it.remove();
322                         if ( it.hasPrevious() )
323                         {
324                             it.previous();
325                             it.remove();
326                         }
327                     }
328                 }
329 
330                 value = StringUtils.join( parts.iterator(), sep );
331             }
332         }
333 
334         if ( finalSep != null && value.length() > 0 && !value.endsWith( finalSep ) )
335         {
336             value += finalSep;
337         }
338 
339         return value;
340     }
341 
342 
343     /**
344      * ORDER OF INTERPOLATION PRECEDENCE:
345      * <ol>
346      * <li>prefixed with "module.", if moduleProject != null
347      * <ol>
348      * <li>Artifact instance for module, if moduleArtifact != null</li>
349      * <li>ArtifactHandler instance for module, if moduleArtifact != null</li>
350      * <li>MavenProject instance for module</li>
351      * </ol>
352      * </li>
353      * <li>prefixed with "artifact."
354      * <ol>
355      * <li>Artifact instance</li>
356      * <li>ArtifactHandler instance for artifact</li>
357      * <li>MavenProject instance for artifact</li>
358      * </ol>
359      * </li>
360      * <li>prefixed with "pom." or "project."
361      * <ol>
362      * <li>MavenProject instance from current build</li>
363      * </ol>
364      * </li>
365      * <li>no prefix, using main project instance
366      * <ol>
367      * <li>MavenProject instance from current build</li>
368      * </ol>
369      * </li>
370      * <li>Support for special expressions, like ${dashClassifier?}</li>
371      * <li>user-defined properties from the command line</li>
372      * <li>properties from main project</li>
373      * <li>system properties, from the MavenSession instance (to support IDEs)</li>
374      * <li>environment variables.</li>
375      * </ol>
376      */
377     public static String evaluateFileNameMapping( final String expression, final Artifact artifact,
378                                                   /* nullable */ final MavenProject mainProject,
379                                                   /* nullable */  final Artifact moduleArtifact,
380                                                   final AssemblerConfigurationSource configSource,
381                                                   FixedStringSearchInterpolator moduleProjectInterpolator,
382                                                   FixedStringSearchInterpolator artifactProjectInterpolator )
383     {
384         String value = expression;
385 
386         final FixedStringSearchInterpolator interpolator =
387             FixedStringSearchInterpolator.create( moduleArtifactInterpolator( moduleArtifact ),
388                                                   moduleProjectInterpolator, artifactInterpolator( artifact ),
389                                                   artifactProjectInterpolator,
390                                                   mainProjectOnlyInterpolator( mainProject ),
391                                                   classifierRules( artifact ),
392                                                   executionPropertiesInterpolator( configSource ),
393                                                   configSource.getMainProjectInterpolator(),
394                                                   configSource.getCommandLinePropsInterpolator(),
395                                                   configSource.getEnvInterpolator() );
396 
397         value = interpolator.interpolate( value );
398 
399         value = StringUtils.replace( value, "//", "/" );
400         value = StringUtils.replace( value, "\\\\", "\\" );
401         value = fixRelativeRefs( value );
402 
403         return value;
404     }
405 
406     /**
407      * ORDER OF INTERPOLATION PRECEDENCE:
408      * <ol>
409      * <li>Support for special expressions, like ${finalName} (use the assembly plugin configuration not the build
410      * config)</li>
411      * <li>prefixed with "module." if moduleProject is non-null
412      * <ol>
413      * <li>MavenProject instance for module being assembled</li>
414      * </ol>
415      * </li>
416      * <li>prefixed with "artifact." if artifactProject is non-null
417      * <ol>
418      * <li>MavenProject instance for artifact</li>
419      * </ol>
420      * </li>
421      * <li>user-defined properties from the command line</li>
422      * <li>prefixed with "pom." or "project.", or no prefix at all
423      * <ol>
424      * <li>MavenProject instance from current build</li>
425      * </ol>
426      * </li>
427      * <li>properties from main project</li>
428      * <li>system properties, from the MavenSession instance (to support IDEs)</li>
429      * <li>environment variables.</li>
430      * </ol>
431      */
432     public static String getOutputDirectory( final String output, final String finalName,
433                                              final AssemblerConfigurationSource configSource,
434                                              FixedStringSearchInterpolator moduleProjectIntrpolator,
435                                              FixedStringSearchInterpolator artifactProjectInterpolator )
436         throws AssemblyFormattingException
437     {
438         String value = output;
439         if ( value == null )
440         {
441             value = "";
442         }
443 
444         final FixedStringSearchInterpolator interpolator =
445             FixedStringSearchInterpolator.create( finalNameInterpolator( finalName ), moduleProjectIntrpolator,
446                                                   artifactProjectInterpolator,
447                                                   executionPropertiesInterpolator( configSource ),
448                                                   configSource.getMainProjectInterpolator(),
449                                                   configSource.getCommandLinePropsInterpolator(),
450                                                   configSource.getEnvInterpolator() );
451 
452         value = interpolator.interpolate( value );
453 
454         if ( ( value.length() > 0 ) && !value.endsWith( "/" ) && !value.endsWith( "\\" ) )
455         {
456             value += "/";
457         }
458 
459         if ( ( value.length() > 0 ) && ( value.startsWith( "/" ) || value.startsWith( "\\" ) ) )
460         {
461             value = value.substring( 1 );
462         }
463 
464         value = StringUtils.replace( value, "//", "/" );
465         value = StringUtils.replace( value, "\\\\", "\\" );
466         value = fixRelativeRefs( value );
467 
468         return value;
469     }
470 
471     public static void warnForPlatformSpecifics( Logger logger, String destDirectory )
472     {
473         if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
474         {
475             if ( isUnixRootReference( destDirectory ) )
476             {
477                 logger.error( "OS=Windows and the assembly descriptor contains a *nix-specific root-relative reference"
478                                   + " (starting with slash): " + destDirectory );
479             }
480             else if ( isWindowsPath( destDirectory ) )
481             {
482                 logger.warn( "The assembly descriptor contains a Windows-specific directory reference"
483                                  + " (with a drive letter). This is not portable and will fail on non-Windows: "
484                                  + destDirectory );
485             }
486         }
487         else
488         {
489             if ( isWindowsPath( destDirectory ) )
490             {
491                 logger.error(
492                     "OS=Non-Windows and the assembly descriptor contains a Windows-specific directory reference"
493                         + " (with a drive letter): " + destDirectory );
494             }
495             else if ( isUnixRootReference( destDirectory ) )
496             {
497                 logger.warn( "The assembly descriptor contains a *nix-specific root-relative reference"
498                                  + " (starting with slash). This is not portable and might fail on Windows: "
499                                  + destDirectory );
500             }
501         }
502     }
503 
504     static boolean isWindowsPath( String destDirectory )
505     {
506         return ( destDirectory != null && destDirectory.length() >= 2 && destDirectory.charAt( 1 ) == ':' );
507     }
508 
509     static boolean isUnixRootReference( String destDirectory )
510     {
511         return ( destDirectory != null && destDirectory.startsWith( "/" ) );
512     }
513 
514 
515 }