View Javadoc

1   package org.apache.maven.shared.release.phase;
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.io.StringReader;
25  import java.io.StringWriter;
26  import java.io.Writer;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.Collection;
30  import java.util.Collections;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.regex.Matcher;
35  import java.util.regex.Pattern;
36  
37  import org.apache.maven.artifact.Artifact;
38  import org.apache.maven.artifact.ArtifactUtils;
39  import org.apache.maven.model.Model;
40  import org.apache.maven.model.Scm;
41  import org.apache.maven.project.MavenProject;
42  import org.apache.maven.scm.ScmException;
43  import org.apache.maven.scm.ScmFileSet;
44  import org.apache.maven.scm.command.edit.EditScmResult;
45  import org.apache.maven.scm.manager.NoSuchScmProviderException;
46  import org.apache.maven.scm.provider.ScmProvider;
47  import org.apache.maven.scm.repository.ScmRepository;
48  import org.apache.maven.scm.repository.ScmRepositoryException;
49  import org.apache.maven.shared.release.ReleaseExecutionException;
50  import org.apache.maven.shared.release.ReleaseFailureException;
51  import org.apache.maven.shared.release.ReleaseResult;
52  import org.apache.maven.shared.release.config.ReleaseDescriptor;
53  import org.apache.maven.shared.release.env.ReleaseEnvironment;
54  import org.apache.maven.shared.release.scm.IdentifiedScm;
55  import org.apache.maven.shared.release.scm.ReleaseScmCommandException;
56  import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
57  import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
58  import org.apache.maven.shared.release.util.ReleaseUtil;
59  import org.codehaus.plexus.interpolation.InterpolationException;
60  import org.codehaus.plexus.interpolation.MapBasedValueSource;
61  import org.codehaus.plexus.interpolation.ObjectBasedValueSource;
62  import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
63  import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
64  import org.codehaus.plexus.interpolation.StringSearchInterpolator;
65  import org.codehaus.plexus.util.IOUtil;
66  import org.codehaus.plexus.util.WriterFactory;
67  import org.jdom.CDATA;
68  import org.jdom.Comment;
69  import org.jdom.Document;
70  import org.jdom.Element;
71  import org.jdom.JDOMException;
72  import org.jdom.Namespace;
73  import org.jdom.Text;
74  import org.jdom.filter.ContentFilter;
75  import org.jdom.filter.ElementFilter;
76  import org.jdom.input.SAXBuilder;
77  import org.jdom.output.Format;
78  import org.jdom.output.XMLOutputter;
79  
80  /**
81   * Base class for rewriting phases.
82   *
83   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
84   */
85  public abstract class AbstractRewritePomsPhase
86      extends AbstractReleasePhase
87  {
88      /**
89       * Tool that gets a configured SCM repository from release configuration.
90       */
91      private ScmRepositoryConfigurator scmRepositoryConfigurator;
92  
93      /**
94       * Configuration item for the suffix to add to rewritten POMs when simulating.
95       */
96      private String pomSuffix;
97  
98      private String ls = ReleaseUtil.LS;
99  
100     public void setLs( String ls )
101     {
102         this.ls = ls;
103     }
104 
105     public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
106                                   List<MavenProject> reactorProjects )
107         throws ReleaseExecutionException, ReleaseFailureException
108     {
109         ReleaseResult result = new ReleaseResult();
110 
111         transform( releaseDescriptor, releaseEnvironment, reactorProjects, false, result );
112 
113         result.setResultCode( ReleaseResult.SUCCESS );
114 
115         return result;
116     }
117 
118     private void transform( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
119                             List<MavenProject> reactorProjects, boolean simulate, ReleaseResult result )
120         throws ReleaseExecutionException, ReleaseFailureException
121     {
122         for ( MavenProject project : reactorProjects )
123         {
124             logInfo( result, "Transforming '" + project.getName() + "'..." );
125 
126             transformProject( project, releaseDescriptor, releaseEnvironment, reactorProjects, simulate, result );
127         }
128     }
129 
130     private void transformProject( MavenProject project, ReleaseDescriptor releaseDescriptor,
131                                    ReleaseEnvironment releaseEnvironment, List<MavenProject> reactorProjects,
132                                    boolean simulate, ReleaseResult result )
133         throws ReleaseExecutionException, ReleaseFailureException
134     {
135         Document document;
136         String intro = null;
137         String outtro = null;
138         try
139         {
140             String content = ReleaseUtil.readXmlFile( ReleaseUtil.getStandardPom( project ), ls );
141             // we need to eliminate any extra whitespace inside elements, as JDOM will nuke it
142             content = content.replaceAll( "<([^!][^>]*?)\\s{2,}([^>]*?)>", "<$1 $2>" );
143             content = content.replaceAll( "(\\s{2,}|[^\\s])/>", "$1 />" );
144 
145             SAXBuilder builder = new SAXBuilder();
146             document = builder.build( new StringReader( content ) );
147 
148             // Normalize line endings to platform's style (XML processors like JDOM normalize line endings to "\n" as
149             // per section 2.11 of the XML spec)
150             normaliseLineEndings( document );
151 
152             // rewrite DOM as a string to find differences, since text outside the root element is not tracked
153             StringWriter w = new StringWriter();
154             Format format = Format.getRawFormat();
155             format.setLineSeparator( ls );
156             XMLOutputter out = new XMLOutputter( format );
157             out.output( document.getRootElement(), w );
158 
159             int index = content.indexOf( w.toString() );
160             if ( index >= 0 )
161             {
162                 intro = content.substring( 0, index );
163                 outtro = content.substring( index + w.toString().length() );
164             }
165             else
166             {
167                 /*
168                  * NOTE: Due to whitespace, attribute reordering or entity expansion the above indexOf test can easily
169                  * fail. So let's try harder. Maybe some day, when JDOM offers a StaxBuilder and this builder employes
170                  * XMLInputFactory2.P_REPORT_PROLOG_WHITESPACE, this whole mess can be avoided.
171                  */
172                 final String SPACE = "\\s++";
173                 final String XML = "<\\?(?:(?:[^\"'>]++)|(?:\"[^\"]*+\")|(?:'[^\']*+'))*+>";
174                 final String INTSUB = "\\[(?:(?:[^\"'\\]]++)|(?:\"[^\"]*+\")|(?:'[^\']*+'))*+\\]";
175                 final String DOCTYPE =
176                     "<!DOCTYPE(?:(?:[^\"'\\[>]++)|(?:\"[^\"]*+\")|(?:'[^\']*+')|(?:" + INTSUB + "))*+>";
177                 final String PI = XML;
178                 final String COMMENT = "<!--(?:[^-]|(?:-[^-]))*+-->";
179 
180                 final String INTRO =
181                     "(?:(?:" + SPACE + ")|(?:" + XML + ")|(?:" + DOCTYPE + ")|(?:" + COMMENT + ")|(?:" + PI + "))*";
182                 final String OUTRO = "(?:(?:" + SPACE + ")|(?:" + COMMENT + ")|(?:" + PI + "))*";
183                 final String POM = "(?s)(" + INTRO + ")(.*?)(" + OUTRO + ")";
184 
185                 Matcher matcher = Pattern.compile( POM ).matcher( content );
186                 if ( matcher.matches() )
187                 {
188                     intro = matcher.group( 1 );
189                     outtro = matcher.group( matcher.groupCount() );
190                 }
191             }
192         }
193         catch ( JDOMException e )
194         {
195             throw new ReleaseExecutionException( "Error reading POM: " + e.getMessage(), e );
196         }
197         catch ( IOException e )
198         {
199             throw new ReleaseExecutionException( "Error reading POM: " + e.getMessage(), e );
200         }
201 
202         ScmRepository scmRepository;
203         ScmProvider provider;
204         try
205         {
206             scmRepository = scmRepositoryConfigurator.getConfiguredRepository( releaseDescriptor,
207                                                                                releaseEnvironment.getSettings() );
208 
209             provider = scmRepositoryConfigurator.getRepositoryProvider( scmRepository );
210         }
211         catch ( ScmRepositoryException e )
212         {
213             throw new ReleaseScmRepositoryException( e.getMessage(), e.getValidationMessages() );
214         }
215         catch ( NoSuchScmProviderException e )
216         {
217             throw new ReleaseExecutionException( "Unable to configure SCM repository: " + e.getMessage(), e );
218         }
219 
220         transformDocument( project, document.getRootElement(), releaseDescriptor, reactorProjects, scmRepository,
221                            result, simulate );
222 
223         File pomFile = ReleaseUtil.getStandardPom( project );
224 
225         if ( simulate )
226         {
227             File outputFile = new File( pomFile.getParentFile(), pomFile.getName() + "." + pomSuffix );
228             writePom( outputFile, document, releaseDescriptor, project.getModelVersion(), intro, outtro );
229         }
230         else
231         {
232             writePom( pomFile, document, releaseDescriptor, project.getModelVersion(), intro, outtro, scmRepository,
233                       provider );
234         }
235     }
236 
237     private void normaliseLineEndings( Document document )
238     {
239         for ( Iterator<?> i = document.getDescendants( new ContentFilter( ContentFilter.COMMENT ) ); i.hasNext(); )
240         {
241             Comment c = (Comment) i.next();
242             c.setText( ReleaseUtil.normalizeLineEndings( c.getText(), ls ) );
243         }
244         for ( Iterator<?> i = document.getDescendants( new ContentFilter( ContentFilter.CDATA ) ); i.hasNext(); )
245         {
246             CDATA c = (CDATA) i.next();
247             c.setText( ReleaseUtil.normalizeLineEndings( c.getText(), ls ) );
248         }
249     }
250 
251     private void transformDocument( MavenProject project, Element rootElement, ReleaseDescriptor releaseDescriptor,
252                                     List<MavenProject> reactorProjects, ScmRepository scmRepository, ReleaseResult result,
253                                     boolean simulate )
254         throws ReleaseExecutionException, ReleaseFailureException
255     {
256         Namespace namespace = rootElement.getNamespace();
257         Map<String, String> mappedVersions = getNextVersionMap( releaseDescriptor );
258         Map<String, String> originalVersions = getOriginalVersionMap( releaseDescriptor, reactorProjects, simulate );
259         @SuppressWarnings("unchecked")
260 		Map<String, Map<String, String>> resolvedSnapshotDependencies = releaseDescriptor.getResolvedSnapshotDependencies();
261         Model model = project.getModel();
262         Element properties = rootElement.getChild( "properties", namespace );
263 
264         String parentVersion = rewriteParent( project, rootElement, namespace, mappedVersions, 
265                                               resolvedSnapshotDependencies, originalVersions );
266 
267         String projectId = ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() );
268 
269         rewriteVersion( rootElement, namespace, mappedVersions, projectId, project, parentVersion );
270 
271         List<Element> roots = new ArrayList<Element>();
272         roots.add( rootElement );
273         roots.addAll( getChildren( rootElement, "profiles", "profile" ) );
274 
275         for ( Element root : roots )
276         {
277             rewriteArtifactVersions( getChildren( root, "dependencies", "dependency" ), mappedVersions,
278                                     resolvedSnapshotDependencies, originalVersions, model, properties, result,
279                                     releaseDescriptor );
280 
281             rewriteArtifactVersions( getChildren( root, "dependencyManagement", "dependencies", "dependency" ),
282                                     mappedVersions, resolvedSnapshotDependencies, originalVersions, model, properties,
283                                     result, releaseDescriptor );
284 
285             rewriteArtifactVersions( getChildren( root, "build", "extensions", "extension" ), mappedVersions,
286                                     resolvedSnapshotDependencies, originalVersions, model, properties, result,
287                                     releaseDescriptor );
288 
289             List<Element> pluginElements = new ArrayList<Element>();
290             pluginElements.addAll( getChildren( root, "build", "plugins", "plugin" ) );
291             pluginElements.addAll( getChildren( root, "build", "pluginManagement", "plugins", "plugin" ) );
292 
293             rewriteArtifactVersions( pluginElements, mappedVersions, resolvedSnapshotDependencies, originalVersions,
294                                     model, properties, result, releaseDescriptor );
295 
296             for ( Element pluginElement : pluginElements )
297             {
298                 rewriteArtifactVersions( getChildren( pluginElement, "dependencies", "dependency" ), mappedVersions,
299                                         resolvedSnapshotDependencies, originalVersions, model, properties, result,
300                                         releaseDescriptor );
301             }
302 
303             rewriteArtifactVersions( getChildren( root, "reporting", "plugins", "plugin" ), mappedVersions,
304                                     resolvedSnapshotDependencies, originalVersions, model, properties, result,
305                                     releaseDescriptor );
306         }
307 
308         String commonBasedir;
309         try
310         {
311             commonBasedir = ReleaseUtil.getCommonBasedir( reactorProjects );
312         }
313         catch ( IOException e )
314         {
315             throw new ReleaseExecutionException( "Exception occurred while calculating common basedir: "
316                 + e.getMessage(), e );
317         }
318         transformScm( project, rootElement, namespace, releaseDescriptor, projectId, scmRepository, result,
319                       commonBasedir );
320     }
321 
322     @SuppressWarnings( "unchecked" )
323     private List<Element> getChildren( Element root, String... names )
324     {
325         Element parent = root;
326         for ( int i = 0; i < names.length - 1 && parent != null; i++ )
327         {
328             parent = parent.getChild( names[i], parent.getNamespace() );
329         }
330         if ( parent == null )
331         {
332             return Collections.emptyList();
333         }
334         return parent.getChildren( names[names.length - 1], parent.getNamespace() );
335     }
336 
337     /**
338      * Updates the text value of the given element. The primary purpose of this method is to preserve any whitespace and
339      * comments around the original text value.
340      *
341      * @param element The element to update, must not be <code>null</code>.
342      * @param value   The text string to set, must not be <code>null</code>.
343      */
344     private void rewriteValue( Element element, String value )
345     {
346         Text text = null;
347         if ( element.getContent() != null )
348         {
349             for ( Iterator<?> it = element.getContent().iterator(); it.hasNext(); )
350             {
351                 Object content = it.next();
352                 if ( ( content instanceof Text ) && ( (Text) content ).getTextTrim().length() > 0 )
353                 {
354                     text = (Text) content;
355                     while ( it.hasNext() )
356                     {
357                         content = it.next();
358                         if ( content instanceof Text )
359                         {
360                             text.append( (Text) content );
361                             it.remove();
362                         }
363                         else
364                         {
365                             break;
366                         }
367                     }
368                     break;
369                 }
370             }
371         }
372         if ( text == null )
373         {
374             element.addContent( value );
375         }
376         else
377         {
378             String chars = text.getText();
379             String trimmed = text.getTextTrim();
380             int idx = chars.indexOf( trimmed );
381             String leadingWhitespace = chars.substring( 0, idx );
382             String trailingWhitespace = chars.substring( idx + trimmed.length() );
383             text.setText( leadingWhitespace + value + trailingWhitespace );
384         }
385     }
386 
387     private void rewriteVersion( Element rootElement, Namespace namespace, Map<String, String> mappedVersions, String projectId,
388                                  MavenProject project, String parentVersion )
389         throws ReleaseFailureException
390     {
391         Element versionElement = rootElement.getChild( "version", namespace );
392         String version = mappedVersions.get( projectId );
393         if ( version == null )
394         {
395             throw new ReleaseFailureException( "Version for '" + project.getName() + "' was not mapped" );
396         }
397 
398         if ( versionElement == null )
399         {
400             if ( !version.equals( parentVersion ) )
401             {
402                 // we will add this after artifactId, since it was missing but different from the inherited version
403                 Element artifactIdElement = rootElement.getChild( "artifactId", namespace );
404                 int index = rootElement.indexOf( artifactIdElement );
405 
406                 versionElement = new Element( "version", namespace );
407                 versionElement.setText( version );
408                 rootElement.addContent( index + 1, new Text( "\n  " ) );
409                 rootElement.addContent( index + 2, versionElement );
410             }
411         }
412         else
413         {
414             rewriteValue( versionElement, version );
415         }
416     }
417 
418     private String rewriteParent( MavenProject project, Element rootElement, Namespace namespace, Map<String, String> mappedVersions,
419                                   Map<String, Map<String, String>> resolvedSnapshotDependencies, Map<String, String> originalVersions )
420         throws ReleaseFailureException
421     {
422         String parentVersion = null;
423         if ( project.hasParent() )
424         {
425             Element parentElement = rootElement.getChild( "parent", namespace );
426             Element versionElement = parentElement.getChild( "version", namespace );
427             MavenProject parent = project.getParent();
428             String key = ArtifactUtils.versionlessKey( parent.getGroupId(), parent.getArtifactId() );
429             parentVersion = mappedVersions.get( key );
430             if ( parentVersion == null )
431             {
432                 //MRELEASE-317
433                 parentVersion = getResolvedSnapshotVersion( key, resolvedSnapshotDependencies );
434             }
435             if ( parentVersion == null )
436             {
437                 if ( parent.getVersion().equals( originalVersions.get( key ) ) )
438                 {
439                     throw new ReleaseFailureException( "Version for parent '" + parent.getName() + "' was not mapped" );
440                 }
441             }
442             else
443             {
444                 rewriteValue( versionElement, parentVersion );
445             }
446         }
447         return parentVersion;
448     }
449 
450     private void rewriteArtifactVersions( Collection<Element> elements, Map<String, String> mappedVersions,
451                                           Map<String, Map<String, String>> resolvedSnapshotDependencies, Map<String, String> originalVersions,
452                                           Model projectModel, Element properties, ReleaseResult result,
453                                           ReleaseDescriptor releaseDescriptor )
454         throws ReleaseExecutionException, ReleaseFailureException
455     {
456         if ( elements == null )
457         {
458             return;
459         }
460         String projectId = ArtifactUtils.versionlessKey( projectModel.getGroupId(), projectModel.getArtifactId() );
461         for ( Element element : elements )
462         {
463             Element versionElement = element.getChild( "version", element.getNamespace() );
464             if ( versionElement == null )
465             {
466                 // managed dependency or unversioned plugin
467                 continue;
468             }
469             String rawVersion = versionElement.getTextTrim();
470 
471             Element groupIdElement = element.getChild( "groupId", element.getNamespace() );
472             if ( groupIdElement == null )
473             {
474                 if ( "plugin".equals( element.getName() ) )
475                 {
476                     groupIdElement = new Element( "groupId", element.getNamespace() );
477                     groupIdElement.setText( "org.apache.maven.plugins" );
478                 }
479                 else
480                 {
481                     // incomplete dependency
482                     continue;
483                 }
484             }
485             String groupId = interpolate( groupIdElement.getTextTrim(), projectModel );
486 
487             Element artifactIdElement = element.getChild( "artifactId", element.getNamespace() );
488             if ( artifactIdElement == null )
489             {
490                 // incomplete element
491                 continue;
492             }
493             String artifactId = interpolate( artifactIdElement.getTextTrim(), projectModel);
494 
495             String key = ArtifactUtils.versionlessKey( groupId, artifactId );
496             String resolvedSnapshotVersion = getResolvedSnapshotVersion( key, resolvedSnapshotDependencies );
497             String mappedVersion = mappedVersions.get( key );
498             String originalVersion = originalVersions.get( key );
499             if ( originalVersion == null )
500             {
501                 originalVersion = getOriginalResolvedSnapshotVersion( key, resolvedSnapshotDependencies );
502             }
503 
504             // MRELEASE-220
505             if ( mappedVersion != null && mappedVersion.endsWith( Artifact.SNAPSHOT_VERSION )
506                 && !rawVersion.endsWith( Artifact.SNAPSHOT_VERSION ) && !releaseDescriptor.isUpdateDependencies() )
507             {
508                 continue;
509             }
510 
511             if ( mappedVersion != null )
512             {
513                 if ( rawVersion.equals( originalVersion ) )
514                 {
515                     logInfo( result, "  Updating " + artifactId + " to " + mappedVersion );
516                     rewriteValue( versionElement, mappedVersion );
517                 }
518                 else if ( rawVersion.matches( "\\$\\{.+\\}" ) )
519                 {
520                     String expression = rawVersion.substring( 2, rawVersion.length() - 1 );
521 
522                     if ( expression.startsWith( "project." ) || expression.startsWith( "pom." )
523                         || "version".equals( expression ) )
524                     {
525                         if ( !mappedVersion.equals( mappedVersions.get( projectId ) ) )
526                         {
527                             logInfo( result, "  Updating " + artifactId + " to " + mappedVersion );
528                             rewriteValue( versionElement, mappedVersion );
529                         }
530                         else
531                         {
532                             logInfo( result, "  Ignoring artifact version update for expression " + rawVersion );
533                         }
534                     }
535                     else if ( properties != null )
536                     {
537                         // version is an expression, check for properties to update instead
538                         Element property = properties.getChild( expression, properties.getNamespace() );
539                         if ( property != null )
540                         {
541                             String propertyValue = property.getTextTrim();
542 
543                             if ( propertyValue.equals( originalVersion ) )
544                             {
545                                 logInfo( result, "  Updating " + rawVersion + " to " + mappedVersion );
546                                 // change the property only if the property is the same as what's in the reactor
547                                 rewriteValue( property, mappedVersion );
548                             }
549                             else if ( mappedVersion.equals( propertyValue ) )
550                             {
551                                 // this property may have been updated during processing a sibling.
552                                 logInfo( result, "  Ignoring artifact version update for expression " + rawVersion
553                                     + " because it is already updated" );
554                             }
555                             else if ( !mappedVersion.equals( rawVersion ) )
556                             {
557                                 if ( mappedVersion.matches( "\\$\\{project.+\\}" )
558                                     || mappedVersion.matches( "\\$\\{pom.+\\}" ) || "${version}".equals( mappedVersion ) )
559                                 {
560                                     logInfo( result, "  Ignoring artifact version update for expression "
561                                         + mappedVersion );
562                                     // ignore... we cannot update this expression
563                                 }
564                                 else
565                                 {
566                                     // the value of the expression conflicts with what the user wanted to release
567                                     throw new ReleaseFailureException( "The artifact (" + key + ") requires a "
568                                         + "different version (" + mappedVersion + ") than what is found ("
569                                         + propertyValue + ") for the expression (" + expression + ") in the "
570                                         + "project (" + projectId + ")." );
571                                 }
572                             }
573                         }
574                         else
575                         {
576                             // the expression used to define the version of this artifact may be inherited
577                             // TODO needs a better error message, what pom? what dependency?
578                             throw new ReleaseFailureException( "The version could not be updated: " + rawVersion );
579                         }
580                     }
581                 }
582                 else
583                 {
584                     // different/previous version not related to current release
585                 }
586             }
587             else if ( resolvedSnapshotVersion != null )
588             {
589                 logInfo( result, "  Updating " + artifactId + " to " + resolvedSnapshotVersion );
590 
591                 rewriteValue( versionElement, resolvedSnapshotVersion );
592             }
593             else
594             {
595                 // artifact not related to current release
596             }
597         }
598     }
599 
600     private String interpolate( String value, Model model )
601         throws ReleaseExecutionException
602     {
603         if ( value != null && value.contains( "${" ) )
604         {
605             StringSearchInterpolator interpolator = new StringSearchInterpolator();
606             List<String> pomPrefixes = Arrays.asList( "pom.", "project." );
607             interpolator.addValueSource( new PrefixedObjectValueSource( pomPrefixes, model, false ) );
608             interpolator.addValueSource( new MapBasedValueSource( model.getProperties() ) );
609             interpolator.addValueSource( new ObjectBasedValueSource( model ) );
610             try
611             {
612                 value = interpolator.interpolate( value, new PrefixAwareRecursionInterceptor( pomPrefixes ) );
613             }
614             catch ( InterpolationException e )
615             {
616                 throw new ReleaseExecutionException(
617                                                      "Failed to interpolate " + value + " for project " + model.getId(),
618                                                      e );
619             }
620         }
621         return value;
622     }
623 
624     private void writePom( File pomFile, Document document, ReleaseDescriptor releaseDescriptor, String modelVersion,
625                            String intro, String outtro, ScmRepository repository, ScmProvider provider )
626         throws ReleaseExecutionException, ReleaseScmCommandException
627     {
628         try
629         {
630             if ( releaseDescriptor.isScmUseEditMode() || provider.requiresEditMode() )
631             {
632                 EditScmResult result = provider.edit( repository, new ScmFileSet(
633                     new File( releaseDescriptor.getWorkingDirectory() ), pomFile ) );
634 
635                 if ( !result.isSuccess() )
636                 {
637                     throw new ReleaseScmCommandException( "Unable to enable editing on the POM", result );
638                 }
639             }
640         }
641         catch ( ScmException e )
642         {
643             throw new ReleaseExecutionException( "An error occurred enabling edit mode: " + e.getMessage(), e );
644         }
645 
646         writePom( pomFile, document, releaseDescriptor, modelVersion, intro, outtro );
647     }
648 
649     private void writePom( File pomFile, Document document, ReleaseDescriptor releaseDescriptor, String modelVersion,
650                            String intro, String outtro )
651         throws ReleaseExecutionException
652     {
653         Element rootElement = document.getRootElement();
654 
655         if ( releaseDescriptor.isAddSchema() )
656         {
657             Namespace pomNamespace = Namespace.getNamespace( "", "http://maven.apache.org/POM/" + modelVersion );
658             rootElement.setNamespace( pomNamespace );
659             Namespace xsiNamespace = Namespace.getNamespace( "xsi", "http://www.w3.org/2001/XMLSchema-instance" );
660             rootElement.addNamespaceDeclaration( xsiNamespace );
661 
662             if ( rootElement.getAttribute( "schemaLocation", xsiNamespace ) == null )
663             {
664                 rootElement.setAttribute( "schemaLocation", "http://maven.apache.org/POM/" + modelVersion
665                     + " http://maven.apache.org/maven-v" + modelVersion.replace( '.', '_' ) + ".xsd", xsiNamespace );
666             }
667 
668             // the empty namespace is considered equal to the POM namespace, so match them up to avoid extra xmlns=""
669             ElementFilter elementFilter = new ElementFilter( Namespace.getNamespace( "" ) );
670             for ( Iterator<?> i = rootElement.getDescendants( elementFilter ); i.hasNext(); )
671             {
672                 Element e = (Element) i.next();
673                 e.setNamespace( pomNamespace );
674             }
675         }
676 
677         Writer writer = null;
678         try
679         {
680             writer = WriterFactory.newXmlWriter( pomFile );
681 
682             if ( intro != null )
683             {
684                 writer.write( intro );
685             }
686 
687             Format format = Format.getRawFormat();
688             format.setLineSeparator( ls );
689             XMLOutputter out = new XMLOutputter( format );
690             out.output( document.getRootElement(), writer );
691 
692             if ( outtro != null )
693             {
694                 writer.write( outtro );
695             }
696         }
697         catch ( IOException e )
698         {
699             throw new ReleaseExecutionException( "Error writing POM: " + e.getMessage(), e );
700         }
701         finally
702         {
703             IOUtil.close( writer );
704         }
705     }
706 
707     public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
708                                    List<MavenProject> reactorProjects )
709         throws ReleaseExecutionException, ReleaseFailureException
710     {
711         ReleaseResult result = new ReleaseResult();
712 
713         transform( releaseDescriptor, releaseEnvironment, reactorProjects, true, result );
714 
715         result.setResultCode( ReleaseResult.SUCCESS );
716 
717         return result;
718     }
719 
720     public ReleaseResult clean( List<MavenProject> reactorProjects )
721     {
722         ReleaseResult result = new ReleaseResult();
723 
724         super.clean( reactorProjects );
725 
726         if ( reactorProjects != null )
727         {
728             for ( MavenProject project : reactorProjects )
729             {
730                 File pomFile = ReleaseUtil.getStandardPom( project );
731                 // MRELEASE-273 : if no pom
732                 if ( pomFile != null )
733                 {
734                     File file = new File( pomFile.getParentFile(), pomFile.getName() + "." + pomSuffix );
735                     if ( file.exists() )
736                     {
737                         file.delete();
738                     }
739                 }
740             }
741         }
742 
743         result.setResultCode( ReleaseResult.SUCCESS );
744 
745         return result;
746     }
747 
748     protected abstract String getResolvedSnapshotVersion( String artifactVersionlessKey, Map<String, Map<String,String>> resolvedSnapshots );
749 
750     protected abstract Map<String, String> getOriginalVersionMap( ReleaseDescriptor releaseDescriptor, List<MavenProject> reactorProjects,
751                                                   boolean simulate );
752 
753     protected abstract Map<String,String> getNextVersionMap( ReleaseDescriptor releaseDescriptor );
754 
755     protected abstract void transformScm( MavenProject project, Element rootElement, Namespace namespace,
756                                           ReleaseDescriptor releaseDescriptor, String projectId,
757                                           ScmRepository scmRepository, ReleaseResult result, String commonBasedir )
758         throws ReleaseExecutionException;
759 
760     protected String getOriginalResolvedSnapshotVersion( String artifactVersionlessKey, Map<String, Map<String, String>> resolvedSnapshots )
761     {
762         Map<String, String> versionsMap = resolvedSnapshots.get( artifactVersionlessKey );
763 
764         if ( versionsMap != null )
765         {
766             return versionsMap.get( ReleaseDescriptor.ORIGINAL_VERSION );
767         }
768         else
769         {
770             return null;
771         }
772     }
773 
774     protected Element rewriteElement( String name, String value, Element root, Namespace namespace )
775     {
776         Element tagElement = root.getChild( name, namespace );
777         if ( tagElement != null )
778         {
779             if ( value != null )
780             {
781                 rewriteValue( tagElement, value );
782             }
783             else
784             {
785                 int index = root.indexOf( tagElement );
786                 root.removeContent( index );
787                 for ( int i = index - 1; i >= 0; i-- )
788                 {
789                     if ( root.getContent( i ) instanceof Text )
790                     {
791                         root.removeContent( i );
792                     }
793                     else
794                     {
795                         break;
796                     }
797                 }
798             }
799         }
800         else
801         {
802             if ( value != null )
803             {
804                 Element element = new Element( name, namespace );
805                 element.setText( value );
806                 root.addContent( "  " ).addContent( element ).addContent( "\n  " );
807                 tagElement = element;
808             }
809         }
810         return tagElement;
811     }
812  
813     protected Scm buildScm( MavenProject project )
814     {
815         IdentifiedScm scm;
816         if ( project.getOriginalModel().getScm() == null )
817         {
818             scm = null;
819         }
820         else
821         {
822             scm = new IdentifiedScm();
823             scm.setConnection( project.getOriginalModel().getScm().getConnection() );
824             scm.setDeveloperConnection( project.getOriginalModel().getScm().getDeveloperConnection() );
825             scm.setTag( project.getOriginalModel().getScm().getTag() );
826             scm.setUrl( project.getOriginalModel().getScm().getUrl() );
827             scm.setId( project.getProperties().getProperty( "project.scm.id" ) );
828         }
829         return scm;
830     }
831 }