1 package org.apache.maven.plugins.gpg;
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.io.FileNotFoundException;
24 import java.io.IOException;
25 import java.io.Reader;
26 import java.io.Writer;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30
31 import org.apache.maven.artifact.Artifact;
32 import org.apache.maven.artifact.factory.ArtifactFactory;
33 import org.apache.maven.artifact.metadata.ArtifactMetadata;
34 import org.apache.maven.artifact.repository.ArtifactRepository;
35 import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
36 import org.apache.maven.artifact.repository.MavenArtifactRepository;
37 import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
38 import org.apache.maven.execution.MavenSession;
39 import org.apache.maven.model.InputLocation;
40 import org.apache.maven.model.Model;
41 import org.apache.maven.model.Parent;
42 import org.apache.maven.model.building.DefaultModelBuildingRequest;
43 import org.apache.maven.model.building.ModelBuildingRequest;
44 import org.apache.maven.model.building.ModelProblem;
45 import org.apache.maven.model.building.ModelProblem.Severity;
46 import org.apache.maven.model.building.ModelProblemCollector;
47 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
48 import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
49 import org.apache.maven.model.validation.ModelValidator;
50 import org.apache.maven.plugin.MojoExecutionException;
51 import org.apache.maven.plugin.MojoFailureException;
52 import org.apache.maven.plugins.annotations.Component;
53 import org.apache.maven.plugins.annotations.Mojo;
54 import org.apache.maven.plugins.annotations.Parameter;
55 import org.apache.maven.project.MavenProject;
56 import org.apache.maven.project.MavenProjectHelper;
57 import org.apache.maven.project.ProjectBuildingRequest;
58 import org.apache.maven.project.artifact.ProjectArtifactMetadata;
59 import org.apache.maven.shared.transfer.artifact.deploy.ArtifactDeployer;
60 import org.apache.maven.shared.transfer.artifact.deploy.ArtifactDeployerException;
61 import org.codehaus.plexus.util.FileUtils;
62 import org.codehaus.plexus.util.ReaderFactory;
63 import org.codehaus.plexus.util.StringUtils;
64 import org.codehaus.plexus.util.WriterFactory;
65 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
66
67
68
69
70
71
72
73 @Mojo( name = "sign-and-deploy-file", requiresProject = false, threadSafe = true )
74 public class SignAndDeployFileMojo
75 extends AbstractGpgMojo
76 {
77
78
79
80
81 @Parameter( property = "gpg.ascDirectory" )
82 private File ascDirectory;
83
84
85
86
87 @Parameter( defaultValue = "${settings.offline}", readonly = true )
88 private boolean offline;
89
90
91
92
93 @Parameter( property = "groupId" )
94 private String groupId;
95
96
97
98
99 @Parameter( property = "artifactId" )
100 private String artifactId;
101
102
103
104
105 @Parameter( property = "version" )
106 private String version;
107
108
109
110
111
112 @Parameter( property = "packaging" )
113 private String packaging;
114
115
116
117
118 @Parameter( property = "classifier" )
119 private String classifier;
120
121
122
123
124 @Parameter( property = "generatePom.description" )
125 private String description;
126
127
128
129
130 @Parameter( property = "file", required = true )
131 private File file;
132
133
134
135
136 @Parameter( property = "pomFile" )
137 private File pomFile;
138
139
140
141
142 @Parameter( property = "generatePom", defaultValue = "true" )
143 private boolean generatePom;
144
145
146
147
148
149 @Parameter( property = "url", required = true )
150 private String url;
151
152
153
154
155
156 @Parameter( property = "repositoryId", defaultValue = "remote-repository", required = true )
157 private String repositoryId;
158
159
160
161
162 @Parameter( property = "repositoryLayout", defaultValue = "default" )
163 private String repositoryLayout;
164
165
166
167 @Component
168 private ArtifactDeployer deployer;
169
170
171
172 @Parameter( defaultValue = "${localRepository}", required = true, readonly = true )
173 private ArtifactRepository localRepository;
174
175
176
177
178 @Component
179 private ArtifactFactory artifactFactory;
180
181
182
183
184 @Component
185 private ModelValidator modelValidator;
186
187
188
189
190
191
192 @Parameter( defaultValue = "${project}", readonly = true, required = true )
193 private MavenProject project;
194
195
196
197
198 @Parameter( defaultValue = "${session}", readonly = true, required = true )
199 private MavenSession session;
200
201
202
203
204
205
206 @Component
207 private MavenProjectHelper projectHelper;
208
209
210
211
212
213
214 @Parameter( property = "javadoc" )
215 private File javadoc;
216
217
218
219
220
221
222 @Parameter( property = "sources" )
223 private File sources;
224
225
226
227
228
229
230
231 @Parameter( property = "retryFailedDeploymentCount", defaultValue = "1" )
232 private int retryFailedDeploymentCount;
233
234
235
236
237
238
239 @Parameter( property = "updateReleaseInfo", defaultValue = "false" )
240 protected boolean updateReleaseInfo;
241
242
243
244
245
246 @Parameter( property = "types" )
247 private String types;
248
249
250
251
252
253 @Parameter( property = "classifiers" )
254 private String classifiers;
255
256
257
258
259
260 @Parameter( property = "files" )
261 private String files;
262
263 private void initProperties()
264 throws MojoExecutionException
265 {
266
267 if ( pomFile != null )
268 {
269 generatePom = false;
270
271 Model model = readModel( pomFile );
272
273 processModel( model );
274 }
275
276 if ( packaging == null && file != null )
277 {
278 packaging = FileUtils.getExtension( file.getName() );
279 }
280 }
281
282 @Override
283 public void execute()
284 throws MojoExecutionException, MojoFailureException
285 {
286 AbstractGpgSigner signer = newSigner( null );
287 signer.setOutputDirectory( ascDirectory );
288 signer.setBaseDirectory( new File( "" ).getAbsoluteFile() );
289
290 if ( offline )
291 {
292 throw new MojoFailureException( "Cannot deploy artifacts when Maven is in offline mode" );
293 }
294
295 initProperties();
296
297 validateArtifactInformation();
298
299 if ( !file.exists() )
300 {
301 throw new MojoFailureException( file.getPath() + " not found." );
302 }
303
304
305 ArtifactRepository deploymentRepository = createDeploymentArtifactRepository( repositoryId, url );
306
307 if ( StringUtils.isEmpty( deploymentRepository.getProtocol() ) )
308 {
309 throw new MojoFailureException( "No transfer protocol found." );
310 }
311
312 Artifact artifact =
313 artifactFactory.createArtifactWithClassifier( groupId, artifactId, version, packaging, classifier );
314
315 if ( file.equals( getLocalRepoFile( artifact ) ) )
316 {
317 throw new MojoFailureException( "Cannot deploy artifact from the local repository: " + file );
318 }
319 artifact.setFile( file );
320
321 File fileSig = signer.generateSignatureForArtifact( file );
322 ArtifactMetadata metadata = new AscArtifactMetadata( artifact, fileSig, false );
323 artifact.addMetadata( metadata );
324
325 if ( !"pom".equals( packaging ) )
326 {
327 if ( pomFile == null && generatePom )
328 {
329 pomFile = generatePomFile();
330 }
331 if ( pomFile != null )
332 {
333 metadata = new ProjectArtifactMetadata( artifact, pomFile );
334 artifact.addMetadata( metadata );
335
336 fileSig = signer.generateSignatureForArtifact( pomFile );
337 metadata = new AscArtifactMetadata( artifact, fileSig, true );
338 artifact.addMetadata( metadata );
339 }
340 }
341
342 if ( updateReleaseInfo )
343 {
344 artifact.setRelease( true );
345 }
346
347 project.setArtifact( artifact );
348
349 try
350 {
351 deploy( artifact, deploymentRepository );
352 }
353 catch ( ArtifactDeployerException e )
354 {
355 throw new MojoExecutionException( e.getMessage(), e );
356 }
357
358 if ( sources != null )
359 {
360 projectHelper.attachArtifact( project, "jar", "sources", sources );
361 }
362
363 if ( javadoc != null )
364 {
365 projectHelper.attachArtifact( project, "jar", "javadoc", javadoc );
366 }
367
368 if ( files != null )
369 {
370 if ( types == null )
371 {
372 throw new MojoExecutionException( "You must specify 'types' if you specify 'files'" );
373 }
374 if ( classifiers == null )
375 {
376 throw new MojoExecutionException( "You must specify 'classifiers' if you specify 'files'" );
377 }
378 int filesLength = StringUtils.countMatches( files, "," );
379 int typesLength = StringUtils.countMatches( types, "," );
380 int classifiersLength = StringUtils.countMatches( classifiers, "," );
381 if ( typesLength != filesLength )
382 {
383 throw new MojoExecutionException( "You must specify the same number of entries in 'files' and "
384 + "'types' (respectively " + filesLength + " and " + typesLength + " entries )" );
385 }
386 if ( classifiersLength != filesLength )
387 {
388 throw new MojoExecutionException( "You must specify the same number of entries in 'files' and "
389 + "'classifiers' (respectively " + filesLength + " and " + classifiersLength + " entries )" );
390 }
391 int fi = 0;
392 int ti = 0;
393 int ci = 0;
394 for ( int i = 0; i <= filesLength; i++ )
395 {
396 int nfi = files.indexOf( ',', fi );
397 if ( nfi == -1 )
398 {
399 nfi = files.length();
400 }
401 int nti = types.indexOf( ',', ti );
402 if ( nti == -1 )
403 {
404 nti = types.length();
405 }
406 int nci = classifiers.indexOf( ',', ci );
407 if ( nci == -1 )
408 {
409 nci = classifiers.length();
410 }
411 File file = new File( files.substring( fi, nfi ) );
412 if ( !file.isFile() )
413 {
414
415 file = new File( project.getBasedir(), files.substring( fi, nfi ) );
416 }
417 if ( file.isFile() )
418 {
419 if ( StringUtils.isWhitespace( classifiers.substring( ci, nci ) ) )
420 {
421 projectHelper.attachArtifact( project, types.substring( ti, nti ).trim(), file );
422 }
423 else
424 {
425 projectHelper.attachArtifact( project, types.substring( ti, nti ).trim(),
426 classifiers.substring( ci, nci ).trim(), file );
427 }
428 }
429 else
430 {
431 throw new MojoExecutionException( "Specified side artifact " + file + " does not exist" );
432 }
433 fi = nfi + 1;
434 ti = nti + 1;
435 ci = nci + 1;
436 }
437 }
438 else
439 {
440 if ( types != null )
441 {
442 throw new MojoExecutionException( "You must specify 'files' if you specify 'types'" );
443 }
444 if ( classifiers != null )
445 {
446 throw new MojoExecutionException( "You must specify 'files' if you specify 'classifiers'" );
447 }
448 }
449
450 for ( Artifact attached : project.getAttachedArtifacts() )
451 {
452 fileSig = signer.generateSignatureForArtifact( attached.getFile() );
453 attached = new AttachedSignedArtifact( attached, new AscArtifactMetadata( attached, fileSig, false ) );
454 try
455 {
456 deploy( attached, deploymentRepository );
457 }
458 catch ( ArtifactDeployerException e )
459 {
460 throw new MojoExecutionException( "Error deploying attached artifact " + attached.getFile() + ": "
461 + e.getMessage(), e );
462 }
463 }
464
465 }
466
467
468
469
470
471
472
473
474 private File getLocalRepoFile( Artifact artifact )
475 {
476 String path = localRepository.pathOf( artifact );
477 return new File( localRepository.getBasedir(), path );
478 }
479
480
481
482
483
484
485 private void processModel( Model model )
486 {
487 Parent parent = model.getParent();
488
489 if ( this.groupId == null )
490 {
491 this.groupId = model.getGroupId();
492 if ( this.groupId == null && parent != null )
493 {
494 this.groupId = parent.getGroupId();
495 }
496 }
497 if ( this.artifactId == null )
498 {
499 this.artifactId = model.getArtifactId();
500 }
501 if ( this.version == null )
502 {
503 this.version = model.getVersion();
504 if ( this.version == null && parent != null )
505 {
506 this.version = parent.getVersion();
507 }
508 }
509 if ( this.packaging == null )
510 {
511 this.packaging = model.getPackaging();
512 }
513 }
514
515
516
517
518
519
520
521
522 private Model readModel( File pomFile )
523 throws MojoExecutionException
524 {
525 try ( Reader reader = ReaderFactory.newXmlReader( pomFile ) )
526 {
527 final Model model = new MavenXpp3Reader().read( reader );
528 return model;
529 }
530 catch ( FileNotFoundException e )
531 {
532 throw new MojoExecutionException( "POM not found " + pomFile, e );
533 }
534 catch ( IOException e )
535 {
536 throw new MojoExecutionException( "Error reading POM " + pomFile, e );
537 }
538 catch ( XmlPullParserException e )
539 {
540 throw new MojoExecutionException( "Error parsing POM " + pomFile, e );
541 }
542 }
543
544
545
546
547
548
549
550 private File generatePomFile()
551 throws MojoExecutionException
552 {
553 Model model = generateModel();
554
555 try
556 {
557 File tempFile = File.createTempFile( "mvndeploy", ".pom" );
558 tempFile.deleteOnExit();
559
560 try ( Writer fw = WriterFactory.newXmlWriter( tempFile ) )
561 {
562 new MavenXpp3Writer().write( fw, model );
563 }
564
565 return tempFile;
566 }
567 catch ( IOException e )
568 {
569 throw new MojoExecutionException( "Error writing temporary pom file: " + e.getMessage(), e );
570 }
571 }
572
573
574
575
576
577
578 private void validateArtifactInformation()
579 throws MojoFailureException
580 {
581 Model model = generateModel();
582
583 ModelBuildingRequest request = new DefaultModelBuildingRequest()
584 .setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 );
585
586 List<String> result = new ArrayList<>();
587
588 SimpleModelProblemCollector problemCollector = new SimpleModelProblemCollector( result );
589
590 modelValidator.validateEffectiveModel( model, request, problemCollector );
591
592 if ( !result.isEmpty() )
593 {
594 StringBuilder msg = new StringBuilder( "The artifact information is incomplete or not valid:\n" );
595 for ( String e : result )
596 {
597 msg.append( " - " + e + '\n' );
598 }
599 throw new MojoFailureException( msg.toString() );
600 }
601 }
602
603
604
605
606
607
608 private Model generateModel()
609 {
610 Model model = new Model();
611
612 model.setModelVersion( "4.0.0" );
613
614 model.setGroupId( groupId );
615 model.setArtifactId( artifactId );
616 model.setVersion( version );
617 model.setPackaging( packaging );
618
619 model.setDescription( description );
620
621 return model;
622 }
623
624
625
626
627
628
629
630
631 protected void deploy( Artifact artifact,
632 ArtifactRepository deploymentRepository )
633 throws ArtifactDeployerException
634 {
635 final ProjectBuildingRequest buildingRequest = session.getProjectBuildingRequest();
636
637 int retryFailedDeploymentCount = Math.max( 1, Math.min( 10, this.retryFailedDeploymentCount ) );
638 ArtifactDeployerException exception = null;
639 for ( int count = 0; count < retryFailedDeploymentCount; count++ )
640 {
641 try
642 {
643 if ( count > 0 )
644 {
645
646 getLog().info( "Retrying deployment attempt " + ( count + 1 ) + " of " + retryFailedDeploymentCount );
647
648 }
649 deployer.deploy( buildingRequest, deploymentRepository, Collections.singletonList( artifact ) );
650
651 for ( Object o : artifact.getMetadataList() )
652 {
653 ArtifactMetadata metadata = (ArtifactMetadata) o;
654 getLog().info( "Metadata[" + metadata.getKey() + "].filename = " + metadata.getRemoteFilename() );
655 }
656 exception = null;
657 break;
658 }
659 catch ( ArtifactDeployerException e )
660 {
661 if ( count + 1 < retryFailedDeploymentCount )
662 {
663 getLog().warn( "Encountered issue during deployment: " + e.getLocalizedMessage() );
664 getLog().debug( e );
665 }
666 if ( exception == null )
667 {
668 exception = e;
669 }
670 }
671 }
672 if ( exception != null )
673 {
674 throw exception;
675 }
676 }
677
678 protected ArtifactRepository createDeploymentArtifactRepository( String id, String url )
679 {
680 return new MavenArtifactRepository( id, url, new DefaultRepositoryLayout(), new ArtifactRepositoryPolicy(),
681 new ArtifactRepositoryPolicy() );
682 }
683
684 private static class SimpleModelProblemCollector
685 implements ModelProblemCollector
686 {
687
688 private final List<String> result;
689
690 SimpleModelProblemCollector( List<String> result )
691 {
692 this.result = result;
693 }
694
695 public void add( Severity severity, String message, InputLocation location, Exception cause )
696 {
697 if ( !ModelProblem.Severity.WARNING.equals( severity ) )
698 {
699 result.add( message );
700 }
701 }
702
703 }
704 }