View Javadoc

1   package org.apache.maven.project.artifact;
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 org.apache.maven.artifact.Artifact;
23  import org.apache.maven.artifact.deployer.ArtifactDeploymentException;
24  import org.apache.maven.artifact.installer.ArtifactInstallationException;
25  import org.apache.maven.artifact.repository.ArtifactRepository;
26  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
27  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
28  import org.apache.maven.artifact.transform.ArtifactTransformation;
29  import org.apache.maven.model.Model;
30  import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
31  import org.apache.maven.project.DefaultProjectBuilderConfiguration;
32  import org.apache.maven.project.MavenProject;
33  import org.apache.maven.project.ProjectBuilderConfiguration;
34  import org.apache.maven.project.interpolation.ModelInterpolationException;
35  import org.apache.maven.project.interpolation.StringSearchModelInterpolator;
36  import org.codehaus.plexus.interpolation.InterpolationException;
37  import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
38  import org.codehaus.plexus.interpolation.Interpolator;
39  import org.codehaus.plexus.interpolation.RecursionInterceptor;
40  import org.codehaus.plexus.interpolation.StringSearchInterpolator;
41  import org.codehaus.plexus.interpolation.ValueSource;
42  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
43  import org.codehaus.plexus.util.IOUtil;
44  import org.codehaus.plexus.util.ReaderFactory;
45  import org.codehaus.plexus.util.WriterFactory;
46  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
47  
48  import java.io.File;
49  import java.io.IOException;
50  import java.io.Reader;
51  import java.io.Writer;
52  import java.util.ArrayList;
53  import java.util.Iterator;
54  import java.util.List;
55  
56  public class VersionExpressionTransformation
57      extends StringSearchModelInterpolator
58      implements Initializable, ArtifactTransformation
59  {
60  
61      public void transformForDeployment( Artifact artifact, ArtifactRepository remoteRepository,
62                                          ArtifactRepository localRepository )
63          throws ArtifactDeploymentException
64      {
65          ProjectArtifactMetadata metadata = ArtifactWithProject.getProjectArtifactMetadata( artifact );
66          File pomFile;
67          boolean pomArtifact = false;
68          if ( "pom".equals( artifact.getType() ) )
69          {
70              if ( getLogger().isDebugEnabled() )
71              {
72                  getLogger().debug( "On Deploy: Using artifact file for POM: " + artifact );
73              }
74              pomFile = artifact.getFile();
75              pomArtifact = true;
76          }
77          // FIXME: We can't be this smart (yet) since the deployment step transforms from the 
78          // original POM once again and re-installs over the top of the install step.
79  //        else if ( metadata == null || metadata.isVersionExpressionsResolved() )
80  //        {
81  //            return;
82  //        }
83          else if ( metadata != null )
84          {
85              pomFile = metadata.getFile();
86          }
87          else
88          {
89              return;
90          }
91  
92          try
93          {
94              File outFile = transformVersions( pomFile, artifact, localRepository );
95              
96              if ( pomArtifact )
97              {
98                  // FIXME: We need a way to mark a POM artifact as resolved WRT version expressions, so we don't reprocess...
99                  artifact.setFile( outFile );
100             }
101             else
102             {
103                 metadata.setFile( outFile );
104                 metadata.setVersionExpressionsResolved( true );
105             }
106         }
107         catch ( IOException e )
108         {
109             throw new ArtifactDeploymentException( "Failed to read or write POM for version transformation.", e );
110         }
111         catch ( ModelInterpolationException e )
112         {
113             throw new ArtifactDeploymentException( "Failed to interpolate POM versions.", e );
114         }
115     }
116 
117     public void transformForInstall( Artifact artifact, ArtifactRepository localRepository )
118         throws ArtifactInstallationException
119     {
120         ProjectArtifactMetadata metadata = (ProjectArtifactMetadata) artifact.getMetadata( ProjectArtifactMetadata.class );
121         File pomFile;
122         boolean pomArtifact = false;
123         if ( "pom".equals( artifact.getType() ) )
124         {
125             if ( getLogger().isDebugEnabled() )
126             {
127                 getLogger().debug( "On Install: Using artifact file for POM: " + artifact );
128             }
129             pomFile = artifact.getFile();
130             pomArtifact = true;
131         }
132         // FIXME: We can't be this smart (yet) since the deployment step transforms from the 
133         // original POM once again and re-installs over the top of the install step.
134 //        else if ( metadata == null || metadata.isVersionExpressionsResolved() )
135 //        {
136 //            return;
137 //        }
138         else if ( metadata != null )
139         {
140             pomFile = metadata.getFile();
141         }
142         else
143         {
144             return;
145         }
146 
147         try
148         {
149             File outFile = transformVersions( pomFile, artifact, localRepository );
150             
151             if ( pomArtifact )
152             {
153                 // FIXME: We need a way to mark a POM artifact as resolved WRT version expressions, so we don't reprocess...
154                 artifact.setFile( outFile );
155             }
156             else
157             {
158                 metadata.setFile( outFile );
159                 metadata.setVersionExpressionsResolved( true );
160             }
161         }
162         catch ( IOException e )
163         {
164             throw new ArtifactInstallationException( "Failed to read or write POM for version transformation.", e );
165         }
166         catch ( ModelInterpolationException e )
167         {
168             throw new ArtifactInstallationException( "Failed to interpolate POM versions.", e );
169         }
170     }
171 
172     public void transformForResolve( Artifact artifact, List remoteRepositories, ArtifactRepository localRepository )
173         throws ArtifactResolutionException, ArtifactNotFoundException
174     {
175         return;
176     }
177 
178     protected File transformVersions( File pomFile, Artifact artifact, ArtifactRepository localRepository )
179         throws IOException, ModelInterpolationException
180     {
181         ProjectBuilderConfiguration pbConfig;
182         File projectDir;
183         File outputFile;
184         if ( artifact instanceof ArtifactWithProject )
185         {
186             MavenProject project = ( (ArtifactWithProject) artifact ).getProject();
187 
188             projectDir = project.getBasedir();
189             pbConfig = project.getProjectBuilderConfiguration();
190             outputFile = new File( project.getBuild().getDirectory(), "pom-transformed.xml" );
191         }
192         else
193         {
194             if ( getLogger().isDebugEnabled() )
195             {
196                 getLogger().debug(
197                                   "WARNING: Artifact: " + artifact
198                                       + " does not have project-builder metadata (ProjectBuilderConfiguration) associated with it.\n"
199                                       + "Cannot access CLI properties for version transformation." );
200             }
201             
202             pbConfig = new DefaultProjectBuilderConfiguration().setLocalRepository( localRepository );
203             projectDir = pomFile.getAbsoluteFile().getParentFile();
204             outputFile = new File( projectDir, "target/pom-transformed.xml" );
205         }
206 
207         Reader reader = null;
208         Model model;
209         try
210         {
211             reader = ReaderFactory.newXmlReader( pomFile );
212             model = new MavenXpp3Reader().read( reader );
213             
214             interpolateVersions( pomFile, outputFile, model, projectDir, pbConfig );
215         }
216         catch ( XmlPullParserException e )
217         {
218             String message =
219                 "Failed to parse POM for version transformation. Proceeding with original (non-interpolated) POM file.";
220             
221             String detail = "\n\nNOTE: Error was in file: " + pomFile + ", at line: "
222                     + e.getLineNumber() + ", column: " + e.getColumnNumber();
223             
224             if ( getLogger().isDebugEnabled() )
225             {
226                 getLogger().debug( message + detail, e );
227             }
228             else
229             {
230                 getLogger().warn( message + " See debug output for details." );
231             }
232             
233             outputFile = pomFile;
234         }
235         finally
236         {
237             IOUtil.close( reader );
238         }
239         
240         return outputFile;
241     }
242 
243     protected void interpolateVersions( File pomFile, File outputFile, Model model, File projectDir, ProjectBuilderConfiguration config )
244         throws ModelInterpolationException
245     {
246         boolean debugEnabled = getLogger().isDebugEnabled();
247 
248         // NOTE: We want to interpolate version expressions ONLY, and want to do so without requiring the
249         // use of the XPP3 Model reader/writers, which have a tendency to lose XML comments and such.
250         // SOOO, we're using a two-stage string interpolation here. The first stage selects all XML 'version'
251         // elements, and subjects their values to interpolation in the second stage.
252         Interpolator interpolator = new StringSearchInterpolator( "<version>", "</version>" );
253         
254         // The second-stage interpolator is the 'normal' one used in all Model interpolation throughout
255         // maven-project.
256         Interpolator secondaryInterpolator = getInterpolator();
257         
258         // We'll just reuse the recursion interceptor...not sure it makes any difference.
259         RecursionInterceptor recursionInterceptor = getRecursionInterceptor();
260         
261         // This is a ValueSource implementation that simply delegates to the second-stage "real" interpolator
262         // once we've isolated the version elements from the input XML.
263         interpolator.addValueSource( new SecondaryInterpolationValueSource( secondaryInterpolator, recursionInterceptor ) );
264         
265         // The primary interpolator is searching for version XML elements, and interpolating their values. Since
266         // '<version>' and '</version>' are the delimiters for this, the interpolator will remove these tokens
267         // from the result. So, we need to put them back before including the interpolated result.
268         interpolator.addPostProcessor( new VersionRestoringPostProcessor() );
269 
270         List valueSources = createValueSources( model, projectDir, config );
271         List postProcessors = createPostProcessors( model, projectDir, config );
272 
273         synchronized ( this )
274         {
275             for ( Iterator it = valueSources.iterator(); it.hasNext(); )
276             {
277                 ValueSource vs = (ValueSource) it.next();
278                 secondaryInterpolator.addValueSource( vs );
279             }
280 
281             for ( Iterator it = postProcessors.iterator(); it.hasNext(); )
282             {
283                 InterpolationPostProcessor postProcessor = (InterpolationPostProcessor) it.next();
284 
285                 secondaryInterpolator.addPostProcessor( postProcessor );
286             }
287 
288             String pomContents;
289             try
290             {
291                 Reader reader = null;
292                 try
293                 {
294                     reader = ReaderFactory.newXmlReader( pomFile );
295                     pomContents = IOUtil.toString( reader );
296                 }
297                 catch ( IOException e )
298                 {
299                     throw new ModelInterpolationException( "Error reading POM for version-expression interpolation: " + e.getMessage(), e );
300                 }
301                 finally
302                 {
303                     IOUtil.close( reader );
304                 }
305                 
306                 try
307                 {
308                     pomContents = interpolator.interpolate( pomContents );
309                 }
310                 catch ( InterpolationException e )
311                 {
312                     throw new ModelInterpolationException( e.getMessage(), e );
313                 }
314 
315                 if ( debugEnabled )
316                 {
317                     List feedback = interpolator.getFeedback();
318                     if ( feedback != null && !feedback.isEmpty() )
319                     {
320                         getLogger().debug( "Maven encountered the following problems while transforming POM versions:" );
321 
322                         Object last = null;
323                         for ( Iterator it = feedback.iterator(); it.hasNext(); )
324                         {
325                             Object next = it.next();
326 
327                             if ( next instanceof Throwable )
328                             {
329                                 if ( last == null )
330                                 {
331                                     getLogger().debug( "", ( (Throwable) next ) );
332                                 }
333                                 else
334                                 {
335                                     getLogger().debug( String.valueOf( last ), ( (Throwable) next ) );
336                                 }
337                             }
338                             else
339                             {
340                                 if ( last != null )
341                                 {
342                                     getLogger().debug( String.valueOf( last ) );
343                                 }
344 
345                                 last = next;
346                             }
347                         }
348 
349                         if ( last != null )
350                         {
351                             getLogger().debug( String.valueOf( last ) );
352                         }
353                     }
354                 }
355 
356                 interpolator.clearFeedback();
357             }
358             finally
359             {
360                 for ( Iterator iterator = valueSources.iterator(); iterator.hasNext(); )
361                 {
362                     ValueSource vs = (ValueSource) iterator.next();
363                     secondaryInterpolator.removeValuesSource( vs );
364                 }
365 
366                 for ( Iterator iterator = postProcessors.iterator(); iterator.hasNext(); )
367                 {
368                     InterpolationPostProcessor postProcessor = (InterpolationPostProcessor) iterator.next();
369                     secondaryInterpolator.removePostProcessor( postProcessor );
370                 }
371 
372                 getInterpolator().clearAnswers();
373             }
374             
375             Writer writer = null;
376             try
377             {
378                 outputFile.getParentFile().mkdirs();
379                 
380                 writer = WriterFactory.newXmlWriter( outputFile );
381                 
382                 IOUtil.copy( pomContents, writer );
383             }
384             catch ( IOException e )
385             {
386                 throw new ModelInterpolationException( "Failed to write transformed POM: " + outputFile.getAbsolutePath(), e );
387             }
388             finally
389             {
390                 IOUtil.close( writer );
391             }
392         }
393         
394         // if ( error != null )
395         // {
396         // throw error;
397         // }
398     }
399 
400     private static final class SecondaryInterpolationValueSource
401         implements ValueSource
402     {
403         
404         private Interpolator secondary;
405         private final RecursionInterceptor recursionInterceptor;
406         private List localFeedback = new ArrayList();
407         
408         public SecondaryInterpolationValueSource( Interpolator secondary, RecursionInterceptor recursionInterceptor )
409         {
410             this.secondary = secondary;
411             this.recursionInterceptor = recursionInterceptor;
412         }
413 
414         public void clearFeedback()
415         {
416             secondary.clearFeedback();
417         }
418 
419         public List getFeedback()
420         {
421             List result = secondary.getFeedback();
422             if ( result != null )
423             {
424                 result = new ArrayList( result );
425             }
426             
427             result.addAll( localFeedback );
428             
429             return result;
430         }
431 
432         public Object getValue( String expression )
433         {
434             try
435             {
436                 return secondary.interpolate( expression, recursionInterceptor );
437             }
438             catch ( InterpolationException e )
439             {
440                 localFeedback.add( "Error during version expression interpolation." );
441                 localFeedback.add( e );
442             }
443             
444             return null;
445         }
446     }
447     
448     private static final class VersionRestoringPostProcessor
449         implements InterpolationPostProcessor
450     {
451 
452         public Object execute( String expression, Object value )
453         {
454             return "<version>" + value + "</version>";
455         }
456         
457     }
458 
459 }