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