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