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