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