View Javadoc

1   package org.apache.maven.plugin.gpg;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.artifact.Artifact;
23  import org.apache.maven.artifact.deployer.ArtifactDeployer;
24  import org.apache.maven.artifact.deployer.ArtifactDeploymentException;
25  import org.apache.maven.artifact.factory.ArtifactFactory;
26  import org.apache.maven.artifact.metadata.ArtifactMetadata;
27  import org.apache.maven.artifact.repository.ArtifactRepository;
28  import org.apache.maven.artifact.repository.ArtifactRepositoryFactory;
29  import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
30  import org.apache.maven.model.Model;
31  import org.apache.maven.model.Parent;
32  import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
33  import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
34  import org.apache.maven.plugin.MojoExecutionException;
35  import org.apache.maven.plugin.MojoFailureException;
36  import org.apache.maven.plugins.annotations.Component;
37  import org.apache.maven.plugins.annotations.Mojo;
38  import org.apache.maven.plugins.annotations.Parameter;
39  import org.apache.maven.project.MavenProject;
40  import org.apache.maven.project.MavenProjectHelper;
41  import org.apache.maven.project.artifact.ProjectArtifactMetadata;
42  import org.apache.maven.project.validation.ModelValidationResult;
43  import org.apache.maven.project.validation.ModelValidator;
44  import org.codehaus.plexus.util.FileUtils;
45  import org.codehaus.plexus.util.IOUtil;
46  import org.codehaus.plexus.util.ReaderFactory;
47  import org.codehaus.plexus.util.StringUtils;
48  import org.codehaus.plexus.util.WriterFactory;
49  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
50  
51  import java.io.File;
52  import java.io.FileNotFoundException;
53  import java.io.IOException;
54  import java.io.Reader;
55  import java.io.Writer;
56  import java.util.List;
57  import java.util.Map;
58  
59  /**
60   * Signs artifacts and installs the artifact in the remote repository.
61   * 
62   * @author Daniel Kulp
63   * @since 1.0-beta-4
64   */
65  @Mojo( name = "sign-and-deploy-file", requiresProject = false, threadSafe = true )
66  public class SignAndDeployFileMojo
67      extends AbstractGpgMojo
68  {
69  
70      /**
71       * The directory where to store signature files.
72       */
73      @Parameter( property = "gpg.ascDirectory" )
74      private File ascDirectory;
75  
76      /**
77       * Flag whether Maven is currently in online/offline mode.
78       */
79      @Parameter( defaultValue = "${settings.offline}", readonly = true )
80      private boolean offline;
81  
82      /**
83       * GroupId of the artifact to be deployed. Retrieved from POM file if specified.
84       */
85      @Parameter( property = "groupId" )
86      private String groupId;
87  
88      /**
89       * ArtifactId of the artifact to be deployed. Retrieved from POM file if specified.
90       */
91      @Parameter( property = "artifactId" )
92      private String artifactId;
93  
94      /**
95       * Version of the artifact to be deployed. Retrieved from POM file if specified.
96       */
97      @Parameter( property = "version" )
98      private String version;
99  
100     /**
101      * Type of the artifact to be deployed. Retrieved from POM file if specified.
102      * Defaults to file extension if not specified via command line or POM.
103      */
104     @Parameter( property = "packaging" )
105     private String packaging;
106 
107     /**
108      * Add classifier to the artifact
109      */
110     @Parameter( property = "classifier" )
111     private String classifier;
112 
113     /**
114      * Description passed to a generated POM file (in case of generatePom=true).
115      */
116     @Parameter( property = "generatePom.description" )
117     private String description;
118 
119     /**
120      * File to be deployed.
121      */
122     @Parameter( property = "file", required = true )
123     private File file;
124 
125     /**
126      * Location of an existing POM file to be deployed alongside the main artifact, given by the ${file} parameter.
127      */
128     @Parameter( property = "pomFile" )
129     private File pomFile;
130 
131     /**
132      * Upload a POM for this artifact. Will generate a default POM if none is supplied with the pomFile argument.
133      */
134     @Parameter( property = "generatePom", defaultValue = "true" )
135     private boolean generatePom;
136 
137     /**
138      * Whether to deploy snapshots with a unique version or not.
139      */
140     @Parameter( property = "uniqueVersion", defaultValue = "true" )
141     private boolean uniqueVersion;
142 
143     /**
144      * URL where the artifact will be deployed. <br/>
145      * ie ( file:///C:/m2-repo or scp://host.com/path/to/repo )
146      */
147     @Parameter( property = "url", required = true )
148     private String url;
149 
150     /**
151      * Server Id to map on the &lt;id&gt; under &lt;server&gt; section of <code>settings.xml</code>. In most cases, this
152      * parameter will be required for authentication.
153      */
154     @Parameter( property = "repositoryId", defaultValue = "remote-repository", required = true )
155     private String repositoryId;
156 
157     /**
158      * The type of remote repository layout to deploy to. Try <i>legacy</i> for a Maven 1.x-style repository layout.
159      */
160     @Parameter( property = "repositoryLayout", defaultValue = "default" )
161     private String repositoryLayout;
162 
163     /**
164      */
165     @Component
166     private ArtifactDeployer deployer;
167 
168     /**
169      */
170     @Parameter( defaultValue = "${localRepository}", required = true, readonly = true )
171     private ArtifactRepository localRepository;
172 
173     /**
174      * Map that contains the layouts.
175      */
176     @Component( role = ArtifactRepositoryLayout.class )
177     private Map repositoryLayouts;
178 
179     /**
180      * Component used to create an artifact
181      */
182     @Component
183     private ArtifactFactory artifactFactory;
184 
185     /**
186      * Component used to create a repository
187      */
188     @Component
189     private ArtifactRepositoryFactory repositoryFactory;
190 
191     /**
192      * The component used to validate the user-supplied artifact coordinates.
193      */
194     @Component
195     private ModelValidator modelValidator;
196 
197     /**
198      * The default Maven project created when building the plugin
199      *
200      * @since 1.3
201      */
202     @Component
203     private MavenProject project;
204 
205     /**
206      * Used for attaching the source and javadoc jars to the project.
207      *
208      * @since 1.3
209      */
210     @Component
211     private MavenProjectHelper projectHelper;
212 
213     /**
214      * The bundled API docs for the artifact.
215      *
216      * @since 1.3
217      */
218     @Parameter( property = "javadoc" )
219     private File javadoc;
220 
221     /**
222      * The bundled sources for the artifact.
223      *
224      * @since 1.3
225      */
226     @Parameter( property = "sources" )
227     private File sources;
228 
229     /**
230      * Parameter used to control how many times a failed deployment will be retried before giving up and failing.
231      * If a value outside the range 1-10 is specified it will be pulled to the nearest value within the range 1-10.
232      *
233      * @since 1.3
234      */
235     @Parameter( property = "retryFailedDeploymentCount", defaultValue = "1" )
236     private int retryFailedDeploymentCount;
237 
238     /**
239      * Parameter used to update the metadata to make the artifact as release.
240      *
241      * @since 1.3
242      */
243     @Parameter( property = "updateReleaseInfo", defaultValue = "false" )
244     protected boolean updateReleaseInfo;
245 
246     /**
247      * A comma separated list of types for each of the extra side artifacts to deploy. If there is a mis-match in
248      * the number of entries in {@link #files} or {@link #classifiers}, then an error will be raised.
249      */
250     @Parameter( property = "types" )
251     private String types;
252 
253     /**
254      * A comma separated list of classifiers for each of the extra side artifacts to deploy. If there is a mis-match in
255      * the number of entries in {@link #files} or {@link #types}, then an error will be raised.
256      */
257     @Parameter( property = "classifiers" )
258     private String classifiers;
259 
260     /**
261      * A comma separated list of files for each of the extra side artifacts to deploy. If there is a mis-match in
262      * the number of entries in {@link #types} or {@link #classifiers}, then an error will be raised.
263      */
264     @Parameter( property = "files" )
265     private String files;
266 
267     private void initProperties()
268         throws MojoExecutionException
269     {
270         // Process the supplied POM (if there is one)
271         if ( pomFile != null )
272         {
273             generatePom = false;
274 
275             Model model = readModel( pomFile );
276 
277             processModel( model );
278         }
279 
280         if ( packaging == null && file != null )
281         {
282             packaging = FileUtils.getExtension( file.getName() );
283         }
284     }
285 
286     public void execute()
287         throws MojoExecutionException, MojoFailureException
288     {
289         AbstractGpgSigner signer = newSigner( null );
290         signer.setOutputDirectory( ascDirectory );
291         signer.setBaseDirectory( new File( "" ).getAbsoluteFile() );
292 
293         if ( offline )
294         {
295             throw new MojoFailureException( "Cannot deploy artifacts when Maven is in offline mode" );
296         }
297 
298         initProperties();
299 
300         validateArtifactInformation();
301 
302         if ( !file.exists() )
303         {
304             throw new MojoFailureException( file.getPath() + " not found." );
305         }
306 
307         ArtifactRepositoryLayout layout = (ArtifactRepositoryLayout) repositoryLayouts.get( repositoryLayout );
308         if ( layout == null )
309         {
310             throw new MojoFailureException( "Invalid repository layout: " + repositoryLayout );
311         }
312 
313         ArtifactRepository deploymentRepository =
314             repositoryFactory.createDeploymentArtifactRepository( repositoryId, url, layout, uniqueVersion );
315 
316         if ( StringUtils.isEmpty( deploymentRepository.getProtocol() ) )
317         {
318             throw new MojoFailureException( "No transfer protocol found." );
319         }
320 
321         Artifact artifact =
322             artifactFactory.createArtifactWithClassifier( groupId, artifactId, version, packaging, classifier );
323 
324         if ( file.equals( getLocalRepoFile( artifact ) ) )
325         {
326             throw new MojoFailureException( "Cannot deploy artifact from the local repository: " + file );
327         }
328 
329         File fileSig = signer.generateSignatureForArtifact( file );
330         ArtifactMetadata metadata = new AscArtifactMetadata( artifact, fileSig, false );
331         artifact.addMetadata( metadata );
332 
333         if ( !"pom".equals( packaging ) )
334         {
335             if ( pomFile == null && generatePom )
336             {
337                 pomFile = generatePomFile();
338             }
339             if ( pomFile != null )
340             {
341                 metadata = new ProjectArtifactMetadata( artifact, pomFile );
342                 artifact.addMetadata( metadata );
343 
344                 fileSig = signer.generateSignatureForArtifact( pomFile );
345                 metadata = new AscArtifactMetadata( artifact, fileSig, true );
346                 artifact.addMetadata( metadata );
347             }
348         }
349 
350         if ( updateReleaseInfo )
351         {
352             artifact.setRelease( true );
353         }
354 
355         project.setArtifact( artifact );
356 
357         try
358         {
359             deploy( file, artifact, deploymentRepository, localRepository );
360         }
361         catch ( ArtifactDeploymentException e )
362         {
363             throw new MojoExecutionException( e.getMessage(), e );
364         }
365 
366         if ( sources != null )
367         {
368             projectHelper.attachArtifact( project, "jar", "sources", sources );
369         }
370 
371         if ( javadoc != null )
372         {
373             projectHelper.attachArtifact( project, "jar", "javadoc", javadoc );
374         }
375 
376         if ( files != null )
377         {
378             if ( types == null )
379             {
380                 throw new MojoExecutionException( "You must specify 'types' if you specify 'files'" );
381             }
382             if ( classifiers == null )
383             {
384                 throw new MojoExecutionException( "You must specify 'classifiers' if you specify 'files'" );
385             }
386             int filesLength = StringUtils.countMatches( files, "," );
387             int typesLength = StringUtils.countMatches( types, "," );
388             int classifiersLength = StringUtils.countMatches( classifiers, "," );
389             if ( typesLength != filesLength )
390             {
391                 throw new MojoExecutionException( "You must specify the same number of entries in 'files' and " +
392                         "'types' (respectively " + filesLength + " and " + typesLength + " entries )" );
393             }
394             if ( classifiersLength != filesLength )
395             {
396                 throw new MojoExecutionException( "You must specify the same number of entries in 'files' and " +
397                         "'classifiers' (respectively " + filesLength + " and " + classifiersLength + " entries )" );
398             }
399             int fi = 0;
400             int ti = 0;
401             int ci = 0;
402             for ( int i = 0; i <= filesLength; i++ )
403             {
404                 int nfi = files.indexOf( ',', fi );
405                 if ( nfi == -1 )
406                 {
407                     nfi = files.length();
408                 }
409                 int nti = types.indexOf( ',', ti );
410                 if ( nti == -1 )
411                 {
412                     nti = types.length();
413                 }
414                 int nci = classifiers.indexOf( ',', ci );
415                 if ( nci == -1 )
416                 {
417                     nci = classifiers.length();
418                 }
419                 File file = new File( files.substring( fi, nfi ) );
420                 if ( !file.isFile() )
421                 {
422                     // try relative to the project basedir just in case
423                     file = new File( project.getBasedir(), files.substring( fi, nfi ) );
424                 }
425                 if ( file.isFile() )
426                 {   
427                     if ( StringUtils.isWhitespace( classifiers.substring( ci, nci ) ) )
428                     {
429                         projectHelper.attachArtifact( project, types.substring( ti, nti ).trim(), file );
430                     }
431                     else
432                     {
433                         projectHelper.attachArtifact( project, types.substring( ti, nti).trim(),
434                                 classifiers.substring( ci, nci ).trim(), file);
435                     } 
436                 }
437                 else
438                 {
439                     throw new MojoExecutionException( "Specified side artifact " + file + " does not exist" );
440                 }
441                 fi = nfi + 1;
442                 ti = nti + 1;
443                 ci = nci + 1;
444             }
445         }   
446         else    
447         {               
448             if ( types != null )
449             {
450                 throw new MojoExecutionException( "You must specify 'files' if you specify 'types'" );
451             }   
452             if ( classifiers != null )
453             {   
454                 throw new MojoExecutionException( "You must specify 'files' if you specify 'classifiers'" );
455             }       
456         }       
457 
458         List attachedArtifacts = project.getAttachedArtifacts();
459 
460         for (Object attachedArtifact : attachedArtifacts) {
461             Artifact attached = (Artifact) attachedArtifact;
462 
463             fileSig = signer.generateSignatureForArtifact(attached.getFile());
464             attached = new AttachedSignedArtifact(attached, new AscArtifactMetadata(attached, fileSig, false));
465             try {
466                 deploy(attached.getFile(), attached, deploymentRepository, localRepository);
467             } catch (ArtifactDeploymentException e) {
468                 throw new MojoExecutionException(
469                         "Error deploying attached artifact " + attached.getFile() + ": " + e.getMessage(), e);
470             }
471         }
472 
473     }
474 
475     /**
476      * Gets the path of the specified artifact within the local repository. Note that the returned path need not exist
477      * (yet).
478      * 
479      * @param artifact The artifact whose local repo path should be determined, must not be <code>null</code>.
480      * @return The absolute path to the artifact when installed, never <code>null</code>.
481      */
482     private File getLocalRepoFile( Artifact artifact )
483     {
484         String path = localRepository.pathOf( artifact );
485         return new File( localRepository.getBasedir(), path );
486     }
487 
488     /**
489      * Process the supplied pomFile to get groupId, artifactId, version, and packaging
490      *
491      * @param model The POM to extract missing artifact coordinates from, must not be <code>null</code>.
492      */
493     private void processModel( Model model )
494     {
495         Parent parent = model.getParent();
496 
497         if ( this.groupId == null )
498         {
499             this.groupId = model.getGroupId();
500             if ( this.groupId == null && parent != null )
501             {
502                 this.groupId = parent.getGroupId();
503             }
504         }
505         if ( this.artifactId == null )
506         {
507             this.artifactId = model.getArtifactId();
508         }
509         if ( this.version == null )
510         {
511             this.version = model.getVersion();
512             if ( this.version == null && parent != null )
513             {
514                 this.version = parent.getVersion();
515             }
516         }
517         if ( this.packaging == null )
518         {
519             this.packaging = model.getPackaging();
520         }
521     }
522 
523     /**
524      * Extract the model from the specified POM file.
525      * 
526      * @param pomFile The path of the POM file to parse, must not be <code>null</code>.
527      * @return The model from the POM file, never <code>null</code>.
528      * @throws MojoExecutionException If the file doesn't exist of cannot be read.
529      */
530     private Model readModel( File pomFile )
531         throws MojoExecutionException
532     {
533         Reader reader = null;
534         try
535         {
536             reader = ReaderFactory.newXmlReader( pomFile );
537             return new MavenXpp3Reader().read( reader );
538         }
539         catch ( FileNotFoundException e )
540         {
541             throw new MojoExecutionException( "POM not found " + pomFile, e );
542         }
543         catch ( IOException e )
544         {
545             throw new MojoExecutionException( "Error reading POM " + pomFile, e );
546         }
547         catch ( XmlPullParserException e )
548         {
549             throw new MojoExecutionException( "Error parsing POM " + pomFile, e );
550         }
551         finally
552         {
553             IOUtil.close( reader );
554         }
555     }
556 
557     /**
558      * Generates a minimal POM from the user-supplied artifact information.
559      * 
560      * @return The path to the generated POM file, never <code>null</code>.
561      * @throws MojoExecutionException If the generation failed.
562      */
563     private File generatePomFile()
564         throws MojoExecutionException
565     {
566         Model model = generateModel();
567 
568         Writer fw = null;
569         try
570         {
571             File tempFile = File.createTempFile( "mvndeploy", ".pom" );
572             tempFile.deleteOnExit();
573 
574             fw = WriterFactory.newXmlWriter( tempFile );
575             new MavenXpp3Writer().write( fw, model );
576 
577             return tempFile;
578         }
579         catch ( IOException e )
580         {
581             throw new MojoExecutionException( "Error writing temporary pom file: " + e.getMessage(), e );
582         }
583         finally
584         {
585             IOUtil.close( fw );
586         }
587     }
588 
589     /**
590      * Validates the user-supplied artifact information.
591      * 
592      * @throws MojoFailureException If any artifact coordinate is invalid.
593      */
594     private void validateArtifactInformation()
595         throws MojoFailureException
596     {
597         Model model = generateModel();
598 
599         ModelValidationResult result = modelValidator.validate( model );
600 
601         if ( result.getMessageCount() > 0 )
602         {
603             throw new MojoFailureException( "The artifact information is incomplete or not valid:\n"
604                 + result.render( "  " ) );
605         }
606     }
607 
608     /**
609      * Generates a minimal model from the user-supplied artifact information.
610      *
611      * @return The generated model, never <code>null</code>.
612      */
613     private Model generateModel()
614     {
615         Model model = new Model();
616 
617         model.setModelVersion( "4.0.0" );
618 
619         model.setGroupId( groupId );
620         model.setArtifactId( artifactId );
621         model.setVersion( version );
622         model.setPackaging( packaging );
623 
624         model.setDescription( description );
625 
626         return model;
627     }
628 
629     /**
630      * Deploy an artifact from a particular file.
631      *
632      * @param source the file to deploy
633      * @param artifact the artifact definition
634      * @param deploymentRepository the repository to deploy to
635      * @param localRepository the local repository to install into
636      * @throws ArtifactDeploymentException if an error occurred deploying the artifact
637      */
638     protected void deploy( File source, Artifact artifact, ArtifactRepository deploymentRepository,
639                            ArtifactRepository localRepository )
640         throws ArtifactDeploymentException
641     {
642         int retryFailedDeploymentCount = Math.max( 1, Math.min( 10, this.retryFailedDeploymentCount ) );
643         ArtifactDeploymentException exception = null;
644         for ( int count = 0; count < retryFailedDeploymentCount; count++ )
645         {
646             try
647             {
648                 if (count > 0)
649                 {
650                     getLog().info(
651                         "Retrying deployment attempt " + ( count + 1 ) + " of " + retryFailedDeploymentCount );
652                 }
653                 deployer.deploy( source, artifact, deploymentRepository, localRepository );
654                 for (Object o : artifact.getMetadataList()) {
655                     ArtifactMetadata metadata = (ArtifactMetadata) o;
656                     getLog().info("Metadata[" + metadata.getKey() + "].filename = " + metadata.getRemoteFilename());
657                 }
658                 exception = null;
659                 break;
660             }
661             catch ( ArtifactDeploymentException e )
662             {
663                 if (count + 1 < retryFailedDeploymentCount) {
664                     getLog().warn( "Encountered issue during deployment: " + e.getLocalizedMessage());
665                     getLog().debug( e );
666                 }
667                 if ( exception == null )
668                 {
669                     exception = e;
670                 }
671             }
672         }
673         if ( exception != null )
674         {
675             throw exception;
676         }
677     }
678 }