1 package org.apache.maven.shared.release.phase;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.text.DateFormat;
24 import java.text.SimpleDateFormat;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Date;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Properties;
31 import java.util.TimeZone;
32
33 import org.apache.maven.artifact.Artifact;
34 import org.apache.maven.artifact.ArtifactUtils;
35 import org.apache.maven.model.Build;
36 import org.apache.maven.model.BuildBase;
37 import org.apache.maven.model.Model;
38 import org.apache.maven.model.ModelBase;
39 import org.apache.maven.model.Plugin;
40 import org.apache.maven.model.Profile;
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.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.transform.ModelETLRequest;
59 import org.apache.maven.shared.release.transform.MavenCoordinate;
60 import org.apache.maven.shared.release.transform.ModelETL;
61 import org.apache.maven.shared.release.transform.ModelETLFactory;
62 import org.apache.maven.shared.release.transform.jdom2.JDomModelETLFactory;
63 import org.apache.maven.shared.release.util.ReleaseUtil;
64 import org.codehaus.plexus.component.annotations.Requirement;
65 import org.codehaus.plexus.util.StringUtils;
66
67
68
69
70
71
72 public abstract class AbstractRewritePomsPhase
73 extends AbstractReleasePhase implements ResourceGenerator
74 {
75
76
77
78 @Requirement
79 private ScmRepositoryConfigurator scmRepositoryConfigurator;
80
81 @Requirement( role = ModelETLFactory.class )
82 private Map<String, ModelETLFactory> modelETLFactories;
83
84
85
86
87 private String modelETL = JDomModelETLFactory.ROLE_HINT;
88
89
90
91
92 @Requirement( role = ScmTranslator.class )
93 private Map<String, ScmTranslator> scmTranslators;
94
95
96
97
98
99
100 protected final Map<String, ScmTranslator> getScmTranslators()
101 {
102 return scmTranslators;
103 }
104
105 private String ls = ReleaseUtil.LS;
106
107
108
109
110
111
112 public void setLs( String ls )
113 {
114 this.ls = ls;
115 }
116
117
118
119
120
121
122 public void setModelETL( String modelETL )
123 {
124 this.modelETL = modelETL;
125 }
126
127 private long startTime = -1 * 1000;
128
129
130
131
132
133
134 public void setStartTime( long startTime )
135 {
136 this.startTime = startTime;
137 }
138
139
140
141
142
143
144 protected abstract String getPomSuffix();
145
146 @Override
147 public ReleaseResult execute( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
148 List<MavenProject> reactorProjects )
149 throws ReleaseExecutionException, ReleaseFailureException
150 {
151 ReleaseResult result = new ReleaseResult();
152
153 transform( releaseDescriptor, releaseEnvironment, reactorProjects, false, result );
154
155 result.setResultCode( ReleaseResult.SUCCESS );
156
157 return result;
158 }
159
160 @Override
161 public ReleaseResult simulate( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
162 List<MavenProject> reactorProjects )
163 throws ReleaseExecutionException, ReleaseFailureException
164 {
165 ReleaseResult result = new ReleaseResult();
166
167 transform( releaseDescriptor, releaseEnvironment, reactorProjects, true, result );
168
169 result.setResultCode( ReleaseResult.SUCCESS );
170
171 return result;
172 }
173
174 @Override
175 public ReleaseResult clean( List<MavenProject> reactorProjects )
176 {
177 ReleaseResult result = new ReleaseResult();
178
179 if ( reactorProjects != null )
180 {
181 for ( MavenProject project : reactorProjects )
182 {
183 File pomFile = ReleaseUtil.getStandardPom( project );
184
185 if ( pomFile != null )
186 {
187 File file = new File( pomFile.getParentFile(), pomFile.getName() + "." + getPomSuffix() );
188 if ( file.exists() )
189 {
190 file.delete();
191 }
192 }
193 }
194 }
195
196 result.setResultCode( ReleaseResult.SUCCESS );
197
198 return result;
199 }
200
201 private void transform( ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment,
202 List<MavenProject> reactorProjects, boolean simulate, ReleaseResult result )
203 throws ReleaseExecutionException, ReleaseFailureException
204 {
205 result.setStartTime( ( startTime >= 0 ) ? startTime : System.currentTimeMillis() );
206
207 for ( MavenProject project : reactorProjects )
208 {
209 logInfo( result, "Transforming '" + project.getName() + "'..." );
210
211 transformProject( project, releaseDescriptor, releaseEnvironment, simulate, result );
212 }
213 }
214
215 private void transformProject( MavenProject project, ReleaseDescriptor releaseDescriptor,
216 ReleaseEnvironment releaseEnvironment, boolean simulate,
217 ReleaseResult result )
218 throws ReleaseExecutionException, ReleaseFailureException
219 {
220 File pomFile = ReleaseUtil.getStandardPom( project );
221
222 ModelETLRequest request = new ModelETLRequest();
223 request.setLineSeparator( ls );
224 request.setProject( project );
225 request.setReleaseDescriptor( releaseDescriptor );
226
227 ModelETL etl = modelETLFactories.get( modelETL ).newInstance( request );
228
229 etl.extract( pomFile );
230
231 ScmRepository scmRepository = null;
232 ScmProvider provider = null;
233
234 if ( isUpdateScm() )
235 {
236 try
237 {
238 scmRepository = scmRepositoryConfigurator.getConfiguredRepository( releaseDescriptor,
239 releaseEnvironment.getSettings() );
240
241 provider = scmRepositoryConfigurator.getRepositoryProvider( scmRepository );
242 }
243 catch ( ScmRepositoryException e )
244 {
245 throw new ReleaseScmRepositoryException( e.getMessage(), e.getValidationMessages() );
246 }
247 catch ( NoSuchScmProviderException e )
248 {
249 throw new ReleaseExecutionException( "Unable to configure SCM repository: " + e.getMessage(), e );
250 }
251 }
252
253 transformDocument( project, etl.getModel(), releaseDescriptor, scmRepository, result,
254 simulate );
255
256 File outputFile;
257 if ( simulate )
258 {
259 outputFile = new File( pomFile.getParentFile(), pomFile.getName() + "." + getPomSuffix() );
260 }
261 else
262 {
263 outputFile = pomFile;
264 prepareScm( pomFile, releaseDescriptor, scmRepository, provider );
265 }
266 etl.load( outputFile );
267
268 }
269
270 private void transformDocument( MavenProject project, Model modelTarget, ReleaseDescriptor releaseDescriptor,
271 ScmRepository scmRepository, ReleaseResult result,
272 boolean simulate )
273 throws ReleaseExecutionException, ReleaseFailureException
274 {
275 Model model = project.getModel();
276
277 Properties properties = modelTarget.getProperties();
278
279 String parentVersion = rewriteParent( project, modelTarget, releaseDescriptor, simulate );
280
281 String projectId = ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() );
282
283 rewriteVersion( modelTarget, releaseDescriptor, projectId, project );
284
285 Build buildTarget = modelTarget.getBuild();
286 if ( buildTarget != null )
287 {
288
289 rewriteArtifactVersions( toMavenCoordinates( buildTarget.getExtensions() ),
290 model, properties, result, releaseDescriptor, simulate );
291
292 rewriteArtifactVersions( toMavenCoordinates( buildTarget.getPlugins() ),
293 model, properties, result, releaseDescriptor, simulate );
294
295 for ( Plugin plugin : buildTarget.getPlugins() )
296 {
297 rewriteArtifactVersions( toMavenCoordinates( plugin.getDependencies() ),
298 model, properties,
299 result, releaseDescriptor, simulate );
300 }
301
302 if ( buildTarget.getPluginManagement() != null )
303 {
304 rewriteArtifactVersions( toMavenCoordinates( buildTarget.getPluginManagement().getPlugins() ), model,
305 properties, result, releaseDescriptor, simulate );
306
307 for ( Plugin plugin : buildTarget.getPluginManagement().getPlugins() )
308 {
309 rewriteArtifactVersions( toMavenCoordinates( plugin.getDependencies() ), model, properties, result,
310 releaseDescriptor, simulate );
311 }
312 }
313 }
314
315 for ( Profile profile : modelTarget.getProfiles() )
316 {
317 BuildBase profileBuild = profile.getBuild();
318 if ( profileBuild != null )
319 {
320 rewriteArtifactVersions( toMavenCoordinates( profileBuild.getPlugins() ), model, properties, result,
321 releaseDescriptor, simulate );
322
323 for ( Plugin plugin : profileBuild.getPlugins() )
324 {
325 rewriteArtifactVersions( toMavenCoordinates( plugin.getDependencies() ), model, properties, result,
326 releaseDescriptor, simulate );
327 }
328
329 if ( profileBuild.getPluginManagement() != null )
330 {
331 rewriteArtifactVersions( toMavenCoordinates( profileBuild.getPluginManagement().getPlugins() ),
332 model, properties, result, releaseDescriptor, simulate );
333
334 for ( Plugin plugin : profileBuild.getPluginManagement().getPlugins() )
335 {
336 rewriteArtifactVersions( toMavenCoordinates( plugin.getDependencies() ), model, properties,
337 result, releaseDescriptor, simulate );
338 }
339 }
340 }
341 }
342
343 List<ModelBase> modelBases = new ArrayList<>();
344 modelBases.add( modelTarget );
345 modelBases.addAll( modelTarget.getProfiles() );
346
347 for ( ModelBase modelBase : modelBases )
348 {
349 rewriteArtifactVersions( toMavenCoordinates( modelBase.getDependencies() ), model, properties, result,
350 releaseDescriptor, simulate );
351
352 if ( modelBase.getDependencyManagement() != null )
353 {
354 rewriteArtifactVersions( toMavenCoordinates( modelBase.getDependencyManagement().getDependencies() ),
355 model, properties, result, releaseDescriptor, simulate );
356 }
357
358 if ( modelBase.getReporting() != null )
359 {
360 rewriteArtifactVersions( toMavenCoordinates( modelBase.getReporting().getPlugins() ), model, properties,
361 result, releaseDescriptor, simulate );
362 }
363 }
364
365 transformScm( project, modelTarget, releaseDescriptor, projectId, scmRepository, result );
366
367 if ( properties != null )
368 {
369 rewriteBuildOutputTimestampProperty( properties, result );
370 }
371 }
372
373 private void rewriteBuildOutputTimestampProperty( Properties properties, ReleaseResult result )
374 {
375 String buildOutputTimestamp = properties.getProperty( "project.build.outputTimestamp" );
376 if ( buildOutputTimestamp == null || StringUtils.isEmpty( buildOutputTimestamp ) )
377 {
378
379 return;
380 }
381
382 if ( StringUtils.isNumeric( buildOutputTimestamp ) )
383 {
384
385 buildOutputTimestamp = String.valueOf( result.getStartTime() / 1000 );
386 }
387 else if ( buildOutputTimestamp.length() <= 1 )
388 {
389
390 return;
391 }
392 else
393 {
394
395 DateFormat df = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss'Z'" );
396 df.setTimeZone( TimeZone.getTimeZone( "UTC" ) );
397 buildOutputTimestamp = df.format( new Date( result.getStartTime() ) );
398 }
399 properties.setProperty( "project.build.outputTimestamp", buildOutputTimestamp );
400 }
401
402 private void rewriteVersion( Model modelTarget, ReleaseDescriptor releaseDescriptor, String projectId,
403 MavenProject project )
404 throws ReleaseFailureException
405 {
406 String version = getNextVersion( releaseDescriptor, projectId );
407 if ( version == null )
408 {
409 throw new ReleaseFailureException( "Version for '" + project.getName() + "' was not mapped" );
410 }
411
412 modelTarget.setVersion( version );
413 }
414
415 private String rewriteParent( MavenProject project, Model targetModel,
416 ReleaseDescriptor releaseDescriptor, boolean simulate )
417 throws ReleaseFailureException
418 {
419 String parentVersion = null;
420 if ( project.hasParent() )
421 {
422 MavenProject parent = project.getParent();
423 String key = ArtifactUtils.versionlessKey( parent.getGroupId(), parent.getArtifactId() );
424 parentVersion = getNextVersion( releaseDescriptor, key );
425 if ( parentVersion == null )
426 {
427
428 parentVersion = getResolvedSnapshotVersion( key, releaseDescriptor );
429 }
430 if ( parentVersion == null )
431 {
432 String original = getOriginalVersion( releaseDescriptor, key, simulate );
433 if ( parent.getVersion().equals( original ) )
434 {
435 throw new ReleaseFailureException( "Version for parent '" + parent.getName() + "' was not mapped" );
436 }
437 }
438 else
439 {
440 targetModel.getParent().setVersion( parentVersion );
441 }
442 }
443 return parentVersion;
444 }
445
446 private void rewriteArtifactVersions( Collection<MavenCoordinate> elements, Model projectModel,
447 Properties properties, ReleaseResult result,
448 ReleaseDescriptor releaseDescriptor, boolean simulate )
449 throws ReleaseExecutionException, ReleaseFailureException
450 {
451 if ( elements == null )
452 {
453 return;
454 }
455 String projectId = ArtifactUtils.versionlessKey( projectModel.getGroupId(), projectModel.getArtifactId() );
456 for ( MavenCoordinate coordinate : elements )
457 {
458 String rawVersion = coordinate.getVersion();
459 if ( rawVersion == null )
460 {
461
462 continue;
463 }
464
465 String rawGroupId = coordinate.getGroupId();
466 if ( rawGroupId == null )
467 {
468 if ( "plugin".equals( coordinate.getName() ) )
469 {
470 rawGroupId = "org.apache.maven.plugins";
471 }
472 else
473 {
474
475 continue;
476 }
477 }
478 String groupId = ReleaseUtil.interpolate( rawGroupId, projectModel );
479
480 String rawArtifactId = coordinate.getArtifactId();
481 if ( rawArtifactId == null )
482 {
483
484 continue;
485 }
486 String artifactId = ReleaseUtil.interpolate( rawArtifactId, projectModel );
487
488 String key = ArtifactUtils.versionlessKey( groupId, artifactId );
489 String resolvedSnapshotVersion = getResolvedSnapshotVersion( key, releaseDescriptor );
490 String mappedVersion = getNextVersion( releaseDescriptor, key );
491 String originalVersion = getOriginalVersion( releaseDescriptor, key, simulate );
492 if ( originalVersion == null )
493 {
494 originalVersion = getOriginalResolvedSnapshotVersion( key, releaseDescriptor );
495 }
496
497
498 if ( mappedVersion != null && mappedVersion.endsWith( Artifact.SNAPSHOT_VERSION )
499 && !rawVersion.endsWith( Artifact.SNAPSHOT_VERSION ) && !releaseDescriptor.isUpdateDependencies() )
500 {
501 continue;
502 }
503
504 if ( mappedVersion != null )
505 {
506 if ( rawVersion.equals( originalVersion ) )
507 {
508 logInfo( result, " Updating " + artifactId + " to " + mappedVersion );
509 coordinate.setVersion( mappedVersion );
510 }
511 else if ( rawVersion.matches( "\\$\\{.+\\}" ) )
512 {
513 String expression = rawVersion.substring( 2, rawVersion.length() - 1 );
514
515 if ( expression.startsWith( "project." ) || expression.startsWith( "pom." )
516 || "version".equals( expression ) )
517 {
518 if ( !mappedVersion.equals( getNextVersion( releaseDescriptor, projectId ) ) )
519 {
520 logInfo( result, " Updating " + artifactId + " to " + mappedVersion );
521 coordinate.setVersion( mappedVersion );
522 }
523 else
524 {
525 logInfo( result, " Ignoring artifact version update for expression " + rawVersion );
526 }
527 }
528 else if ( properties != null )
529 {
530
531
532 String propertyValue = properties.getProperty( expression );
533
534 if ( propertyValue != null )
535 {
536 if ( propertyValue.equals( originalVersion ) )
537 {
538 logInfo( result, " Updating " + rawVersion + " to " + mappedVersion );
539
540 properties.setProperty( expression, mappedVersion );
541 }
542 else if ( mappedVersion.equals( propertyValue ) )
543 {
544
545 logInfo( result, " Ignoring artifact version update for expression " + rawVersion
546 + " because it is already updated" );
547 }
548 else if ( !mappedVersion.equals( rawVersion ) )
549 {
550 if ( mappedVersion.matches( "\\$\\{project.+\\}" )
551 || mappedVersion.matches( "\\$\\{pom.+\\}" )
552 || "${version}".equals( mappedVersion ) )
553 {
554 logInfo( result, " Ignoring artifact version update for expression "
555 + mappedVersion );
556
557 }
558 else
559 {
560
561 throw new ReleaseFailureException( "The artifact (" + key + ") requires a "
562 + "different version (" + mappedVersion + ") than what is found ("
563 + propertyValue + ") for the expression (" + expression + ") in the "
564 + "project (" + projectId + ")." );
565 }
566 }
567 }
568 else
569 {
570
571
572 throw new ReleaseFailureException( "The version could not be updated: " + rawVersion );
573 }
574 }
575 }
576 else
577 {
578
579 }
580 }
581 else if ( resolvedSnapshotVersion != null )
582 {
583 logInfo( result, " Updating " + artifactId + " to " + resolvedSnapshotVersion );
584
585 coordinate.setVersion( resolvedSnapshotVersion );
586 }
587 else
588 {
589
590 }
591 }
592 }
593
594 private void prepareScm( File pomFile, ReleaseDescriptor releaseDescriptor, ScmRepository repository,
595 ScmProvider provider )
596 throws ReleaseExecutionException, ReleaseScmCommandException
597 {
598 try
599 {
600 if ( isUpdateScm() && ( releaseDescriptor.isScmUseEditMode() || provider.requiresEditMode() ) )
601 {
602 EditScmResult result = provider.edit( repository, new ScmFileSet(
603 new File( releaseDescriptor.getWorkingDirectory() ), pomFile ) );
604
605 if ( !result.isSuccess() )
606 {
607 throw new ReleaseScmCommandException( "Unable to enable editing on the POM", result );
608 }
609 }
610 }
611 catch ( ScmException e )
612 {
613 throw new ReleaseExecutionException( "An error occurred enabling edit mode: " + e.getMessage(), e );
614 }
615 }
616
617
618
619
620
621
622
623
624
625 protected abstract String getResolvedSnapshotVersion( String artifactVersionlessKey,
626 ReleaseDescriptor releaseDscriptor );
627
628
629
630
631
632
633
634
635
636 protected abstract String getOriginalVersion( ReleaseDescriptor releaseDescriptor, String projectKey,
637 boolean simulate );
638
639
640
641
642
643
644
645
646 protected abstract String getNextVersion( ReleaseDescriptor releaseDescriptor, String key );
647
648
649
650
651
652
653
654
655
656
657
658
659 protected abstract void transformScm( MavenProject project, Model modelTarget, ReleaseDescriptor releaseDescriptor,
660 String projectId, ScmRepository scmRepository,
661 ReleaseResult result )
662 throws ReleaseExecutionException;
663
664
665
666
667
668
669
670 protected boolean isUpdateScm()
671 {
672 return true;
673 }
674
675
676
677
678
679
680
681
682 protected String getOriginalResolvedSnapshotVersion( String artifactVersionlessKey,
683 ReleaseDescriptor releaseDescriptor )
684 {
685 return releaseDescriptor.getDependencyOriginalVersion( artifactVersionlessKey );
686 }
687
688
689
690
691
692
693
694
695
696
697 protected static String translateUrlPath( String trunkPath, String tagPath, String urlPath )
698 {
699 trunkPath = trunkPath.trim();
700 tagPath = tagPath.trim();
701
702 if ( trunkPath.endsWith( "/" ) )
703 {
704 trunkPath = trunkPath.substring( 0, trunkPath.length() - 1 );
705 }
706 if ( tagPath.endsWith( "/" ) )
707 {
708 tagPath = tagPath.substring( 0, tagPath.length() - 1 );
709 }
710 char[] tagPathChars = trunkPath.toCharArray();
711 char[] trunkPathChars = tagPath.toCharArray();
712
713 int i = 0;
714 while ( ( i < tagPathChars.length ) && ( i < trunkPathChars.length ) && tagPathChars[i] == trunkPathChars[i] )
715 {
716 ++i;
717 }
718
719
720 if ( i == 0 || urlPath.indexOf( trunkPath.substring( i ) ) < 0 )
721 {
722 return tagPath;
723 }
724 else
725 {
726 return StringUtils.replace( urlPath, trunkPath.substring( i ), tagPath.substring( i ) );
727 }
728 }
729
730 private Collection<MavenCoordinate> toMavenCoordinates( List<?> objects )
731 {
732 Collection<MavenCoordinate> coordinates = new ArrayList<>( objects.size() );
733 for ( Object object : objects )
734 {
735 if ( object instanceof MavenCoordinate )
736 {
737 coordinates.add( (MavenCoordinate) object );
738 }
739 else
740 {
741 throw new UnsupportedOperationException();
742 }
743 }
744 return coordinates;
745 }
746
747
748 }