View Javadoc
1   package org.apache.maven.plugins.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.FileOutputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.OutputStream;
28  import java.io.Reader;
29  import java.io.Writer;
30  import java.util.ArrayList;
31  import java.util.Enumeration;
32  import java.util.List;
33  import java.util.jar.JarEntry;
34  import java.util.jar.JarFile;
35  import java.util.regex.Pattern;
36  
37  import org.apache.maven.artifact.Artifact;
38  import org.apache.maven.artifact.repository.ArtifactRepository;
39  import org.apache.maven.model.Model;
40  import org.apache.maven.model.Parent;
41  import org.apache.maven.model.building.ModelBuildingException;
42  import org.apache.maven.model.building.ModelSource;
43  import org.apache.maven.model.building.StringModelSource;
44  import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
45  import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
46  import org.apache.maven.plugin.MojoExecutionException;
47  import org.apache.maven.plugin.MojoFailureException;
48  import org.apache.maven.plugins.annotations.Component;
49  import org.apache.maven.plugins.annotations.Mojo;
50  import org.apache.maven.plugins.annotations.Parameter;
51  import org.apache.maven.project.DefaultProjectBuildingRequest;
52  import org.apache.maven.project.MavenProject;
53  import org.apache.maven.project.MavenProjectHelper;
54  import org.apache.maven.project.ProjectBuilder;
55  import org.apache.maven.project.ProjectBuildingException;
56  import org.apache.maven.project.artifact.ProjectArtifactMetadata;
57  import org.apache.maven.shared.transfer.artifact.DefaultArtifactCoordinate;
58  import org.apache.maven.shared.transfer.artifact.deploy.ArtifactDeployer;
59  import org.apache.maven.shared.transfer.artifact.deploy.ArtifactDeployerException;
60  import org.apache.maven.shared.transfer.repository.RepositoryManager;
61  import org.apache.maven.shared.utils.Os;
62  import org.codehaus.plexus.util.FileUtils;
63  import org.codehaus.plexus.util.IOUtil;
64  import org.codehaus.plexus.util.ReaderFactory;
65  import org.codehaus.plexus.util.StringUtils;
66  import org.codehaus.plexus.util.WriterFactory;
67  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
68  
69  /**
70   * Installs the artifact in the remote repository.
71   * 
72   * @author <a href="mailto:aramirez@apache.org">Allan Ramirez</a>
73   */
74  @Mojo( name = "deploy-file", requiresProject = false, threadSafe = true )
75  public class DeployFileMojo
76      extends AbstractDeployMojo
77  {
78      @Component
79      private ArtifactDeployer artifactDeployer;
80  
81      /**
82       * Used for attaching the artifacts to deploy to the project.
83       */
84      @Component
85      private MavenProjectHelper projectHelper;
86  
87      /**
88       * Used for creating the project to which the artifacts to deploy will be attached.
89       */
90      @Component
91      private ProjectBuilder projectBuilder;
92  
93      /**
94       * GroupId of the artifact to be deployed. Retrieved from POM file if specified.
95       */
96      @Parameter( property = "groupId" )
97      private String groupId;
98  
99      /**
100      * ArtifactId of the artifact to be deployed. Retrieved from POM file if specified.
101      */
102     @Parameter( property = "artifactId" )
103     private String artifactId;
104 
105     /**
106      * Version of the artifact to be deployed. Retrieved from POM file if specified.
107      */
108     @Parameter( property = "version" )
109     private String version;
110 
111     /**
112      * Type of the artifact to be deployed. Retrieved from the &lt;packaging&gt element of the POM file if a POM file
113      * specified. Defaults to the file extension if it is not specified via command line or POM.<br/>
114      * Maven uses two terms to refer to this datum: the &lt;packaging&gt; element for the entire POM, and the
115      * &lt;type&gt; element in a dependency specification.
116      */
117     @Parameter( property = "packaging" )
118     private String packaging;
119 
120     /**
121      * Description passed to a generated POM file (in case of generatePom=true)
122      */
123     @Parameter( property = "generatePom.description" )
124     private String description;
125 
126     /**
127      * File to be deployed.
128      */
129     @Parameter( property = "file", required = true )
130     private File file;
131 
132     /**
133      * The bundled API docs for the artifact.
134      * 
135      * @since 2.6
136      */
137     @Parameter( property = "javadoc" )
138     private File javadoc;
139 
140     /**
141      * The bundled sources for the artifact.
142      * 
143      * @since 2.6
144      */
145     @Parameter( property = "sources" )
146     private File sources;
147 
148     /**
149      * Server Id to map on the &lt;id&gt; under &lt;server&gt; section of settings.xml In most cases, this parameter
150      * will be required for authentication.
151      */
152     @Parameter( property = "repositoryId", defaultValue = "remote-repository", required = true )
153     private String repositoryId;
154 
155     /**
156      * URL where the artifact will be deployed. <br/>
157      * ie ( file:///C:/m2-repo or scp://host.com/path/to/repo )
158      */
159     @Parameter( property = "url", required = true )
160     private String url;
161 
162     /**
163      * Location of an existing POM file to be deployed alongside the main artifact, given by the ${file} parameter.
164      */
165     @Parameter( property = "pomFile" )
166     private File pomFile;
167 
168     /**
169      * Upload a POM for this artifact. Will generate a default POM if none is supplied with the pomFile argument.
170      */
171     @Parameter( property = "generatePom", defaultValue = "true" )
172     private boolean generatePom;
173 
174     /**
175      * Add classifier to the artifact
176      */
177     @Parameter( property = "classifier" )
178     private String classifier;
179 
180     /**
181      * Whether to deploy snapshots with a unique version or not.
182      * 
183      * @deprecated As of Maven 3, this isn't supported anymore and this parameter is only present to break the build if
184      *             you use it!
185      */
186     @Parameter( property = "uniqueVersion" )
187     @Deprecated
188     private Boolean uniqueVersion;
189 
190     /**
191      * A comma separated list of types for each of the extra side artifacts to deploy. If there is a mis-match in the
192      * number of entries in {@link #files} or {@link #classifiers}, then an error will be raised.
193      */
194     @Parameter( property = "types" )
195     private String types;
196 
197     /**
198      * A comma separated list of classifiers for each of the extra side artifacts to deploy. If there is a mis-match in
199      * the number of entries in {@link #files} or {@link #types}, then an error will be raised.
200      */
201     @Parameter( property = "classifiers" )
202     private String classifiers;
203 
204     /**
205      * A comma separated list of files for each of the extra side artifacts to deploy. If there is a mis-match in the
206      * number of entries in {@link #types} or {@link #classifiers}, then an error will be raised.
207      */
208     @Parameter( property = "files" )
209     private String files;
210 
211     @Component
212     private RepositoryManager repoManager;
213 
214     void initProperties()
215         throws MojoExecutionException
216     {
217         if ( pomFile == null )
218         {
219             boolean foundPom = false;
220 
221             JarFile jarFile = null;
222             try
223             {
224                 Pattern pomEntry = Pattern.compile( "META-INF/maven/.*/pom\\.xml" );
225 
226                 jarFile = new JarFile( file );
227 
228                 Enumeration<JarEntry> jarEntries = jarFile.entries();
229 
230                 while ( jarEntries.hasMoreElements() )
231                 {
232                     JarEntry entry = jarEntries.nextElement();
233 
234                     if ( pomEntry.matcher( entry.getName() ).matches() )
235                     {
236                         getLog().debug( "Using " + entry.getName() + " as pomFile" );
237 
238                         foundPom = true;
239 
240                         InputStream pomInputStream = null;
241                         OutputStream pomOutputStream = null;
242 
243                         try
244                         {
245                             pomInputStream = jarFile.getInputStream( entry );
246 
247                             String base = file.getName();
248                             if ( base.indexOf( '.' ) > 0 )
249                             {
250                                 base = base.substring( 0, base.lastIndexOf( '.' ) );
251                             }
252                             pomFile = new File( file.getParentFile(), base + ".pom" );
253 
254                             pomOutputStream = new FileOutputStream( pomFile );
255 
256                             IOUtil.copy( pomInputStream, pomOutputStream );
257 
258                             pomOutputStream.close();
259                             pomOutputStream = null;
260                             pomInputStream.close();
261                             pomInputStream = null;
262 
263                             processModel( readModel( pomFile ) );
264 
265                             break;
266                         }
267                         finally
268                         {
269                             IOUtil.close( pomInputStream );
270                             IOUtil.close( pomOutputStream );
271                         }
272                     }
273                 }
274 
275                 if ( !foundPom )
276                 {
277                     getLog().info( "pom.xml not found in " + file.getName() );
278                 }
279             }
280             catch ( IOException e )
281             {
282                 // ignore, artifact not packaged by Maven
283             }
284             finally
285             {
286                 if ( jarFile != null )
287                 {
288                     try
289                     {
290                         jarFile.close();
291                     }
292                     catch ( IOException e )
293                     {
294                         // we did our best
295                     }
296                 }
297             }
298         }
299         else
300         {
301             processModel( readModel( pomFile ) );
302         }
303 
304         if ( packaging == null && file != null )
305         {
306             packaging = FileUtils.getExtension( file.getName() );
307         }
308     }
309 
310     public void execute()
311         throws MojoExecutionException, MojoFailureException
312     {
313         if ( uniqueVersion != null )
314         {
315             throw new MojoExecutionException( "You are using 'uniqueVersion' which has been removed"
316                 + " from the maven-deploy-plugin. "
317                 + "Please see the >>Major Version Upgrade to version 3.0.0<< on the plugin site." );
318         }
319 
320         failIfOffline();
321 
322         if ( !file.exists() )
323         {
324             throw new MojoExecutionException( file.getPath() + " not found." );
325         }
326 
327         initProperties();
328 
329         ArtifactRepository deploymentRepository = createDeploymentArtifactRepository( repositoryId, url );
330 
331         String protocol = deploymentRepository.getProtocol();
332 
333         if ( StringUtils.isEmpty( protocol ) )
334         {
335             throw new MojoExecutionException( "No transfer protocol found." );
336         }
337 
338         MavenProject project = createMavenProject();
339         Artifact artifact = project.getArtifact();
340 
341         if ( file.equals( getLocalRepoFile() ) )
342         {
343             throw new MojoFailureException( "Cannot deploy artifact from the local repository: " + file );
344         }
345 
346         List<Artifact> deployableArtifacts = new ArrayList<Artifact>();
347 
348         if ( classifier == null )
349         {
350             artifact.setFile( file );
351             deployableArtifacts.add( artifact );
352         }
353         else
354         {
355             projectHelper.attachArtifact( project, packaging, classifier, file );
356         }
357 
358         // Upload the POM if requested, generating one if need be
359         if ( !"pom".equals( packaging ) )
360         {
361             File pom = pomFile;
362             if ( pom == null && generatePom )
363             {
364                 pom = generatePomFile();
365             }
366             if ( pom != null )
367             {
368                 if ( classifier == null )
369                 {
370                     ProjectArtifactMetadata metadata = new ProjectArtifactMetadata( artifact, pom );
371                     artifact.addMetadata( metadata );
372                 }
373                 else
374                 {
375                     artifact.setFile( pom );
376                     deployableArtifacts.add( artifact );
377                 }
378             }
379         }
380 
381         artifact.setRepository( deploymentRepository );
382 
383         if ( sources != null )
384         {
385             projectHelper.attachArtifact( project, "jar", "sources", sources );
386         }
387 
388         if ( javadoc != null )
389         {
390             projectHelper.attachArtifact( project, "jar", "javadoc", javadoc );
391         }
392 
393         if ( files != null )
394         {
395             if ( types == null )
396             {
397                 throw new MojoExecutionException( "You must specify 'types' if you specify 'files'" );
398             }
399             if ( classifiers == null )
400             {
401                 throw new MojoExecutionException( "You must specify 'classifiers' if you specify 'files'" );
402             }
403             int filesLength = StringUtils.countMatches( files, "," );
404             int typesLength = StringUtils.countMatches( types, "," );
405             int classifiersLength = StringUtils.countMatches( classifiers, "," );
406             if ( typesLength != filesLength )
407             {
408                 throw new MojoExecutionException( "You must specify the same number of entries in 'files' and "
409                     + "'types' (respectively " + filesLength + " and " + typesLength + " entries )" );
410             }
411             if ( classifiersLength != filesLength )
412             {
413                 throw new MojoExecutionException( "You must specify the same number of entries in 'files' and "
414                     + "'classifiers' (respectively " + filesLength + " and " + classifiersLength + " entries )" );
415             }
416             int fi = 0;
417             int ti = 0;
418             int ci = 0;
419             for ( int i = 0; i <= filesLength; i++ )
420             {
421                 int nfi = files.indexOf( ',', fi );
422                 if ( nfi == -1 )
423                 {
424                     nfi = files.length();
425                 }
426                 int nti = types.indexOf( ',', ti );
427                 if ( nti == -1 )
428                 {
429                     nti = types.length();
430                 }
431                 int nci = classifiers.indexOf( ',', ci );
432                 if ( nci == -1 )
433                 {
434                     nci = classifiers.length();
435                 }
436                 File file = new File( files.substring( fi, nfi ) );
437                 if ( !file.isFile() )
438                 {
439                     // try relative to the project basedir just in case
440                     file = new File( project.getBasedir(), files.substring( fi, nfi ) );
441                 }
442                 if ( file.isFile() )
443                 {
444                     if ( StringUtils.isWhitespace( classifiers.substring( ci, nci ) ) )
445                     {
446                         projectHelper.attachArtifact( project, types.substring( ti, nti ).trim(), file );
447                     }
448                     else
449                     {
450                         projectHelper.attachArtifact( project, types.substring( ti, nti ).trim(),
451                                                       classifiers.substring( ci, nci ).trim(), file );
452                     }
453                 }
454                 else
455                 {
456                     throw new MojoExecutionException( "Specified side artifact " + file + " does not exist" );
457                 }
458                 fi = nfi + 1;
459                 ti = nti + 1;
460                 ci = nci + 1;
461             }
462         }
463         else
464         {
465             if ( types != null )
466             {
467                 throw new MojoExecutionException( "You must specify 'files' if you specify 'types'" );
468             }
469             if ( classifiers != null )
470             {
471                 throw new MojoExecutionException( "You must specify 'files' if you specify 'classifiers'" );
472             }
473         }
474 
475         List<Artifact> attachedArtifacts = project.getAttachedArtifacts();
476 
477         for ( Artifact attached : attachedArtifacts )
478         {
479             deployableArtifacts.add( attached );
480         }
481 
482         try
483         {
484             artifactDeployer.deploy( getSession().getProjectBuildingRequest(), deploymentRepository,
485                                      deployableArtifacts );
486         }
487         catch ( ArtifactDeployerException e )
488         {
489             throw new MojoExecutionException( e.getMessage(), e );
490         }
491     }
492 
493     /**
494      * Creates a Maven project in-memory from the user-supplied groupId, artifactId and version. When a classifier is
495      * supplied, the packaging must be POM because the project with only have attachments. This project serves as basis
496      * to attach the artifacts to deploy to.
497      * 
498      * @return The created Maven project, never <code>null</code>.
499      * @throws MojoExecutionException When the model of the project could not be built.
500      * @throws MojoFailureException When building the project failed.
501      */
502     private MavenProject createMavenProject()
503         throws MojoExecutionException, MojoFailureException
504     {
505         if ( groupId == null || artifactId == null || version == null || packaging == null )
506         {
507             throw new MojoExecutionException( "The artifact information is incomplete: 'groupId', 'artifactId', "
508                 + "'version' and 'packaging' are required." );
509         }
510         ModelSource modelSource =
511             new StringModelSource( "<project>" + "<modelVersion>4.0.0</modelVersion>" + "<groupId>" + groupId
512                 + "</groupId>" + "<artifactId>" + artifactId + "</artifactId>" + "<version>" + version + "</version>"
513                 + "<packaging>" + ( classifier == null ? packaging : "pom" ) + "</packaging>" + "</project>" );
514         DefaultProjectBuildingRequest buildingRequest =
515             new DefaultProjectBuildingRequest( getSession().getProjectBuildingRequest() );
516         buildingRequest.setProcessPlugins( false );
517         try
518         {
519             return projectBuilder.build( modelSource, buildingRequest ).getProject();
520         }
521         catch ( ProjectBuildingException e )
522         {
523             if ( e.getCause() instanceof ModelBuildingException )
524             {
525                 throw new MojoExecutionException( "The artifact information is not valid:" + Os.LINE_SEP
526                     + e.getCause().getMessage() );
527             }
528             throw new MojoFailureException( "Unable to create the project.", e );
529         }
530     }
531 
532     /**
533      * Gets the path of the artifact constructed from the supplied groupId, artifactId, version, classifier and
534      * packaging within the local repository. Note that the returned path need not exist (yet).
535      * 
536      * @return The absolute path to the artifact when installed, never <code>null</code>.
537      */
538     private File getLocalRepoFile()
539     {
540         DefaultArtifactCoordinate coordinate = new DefaultArtifactCoordinate();
541         coordinate.setGroupId( groupId );
542         coordinate.setArtifactId( artifactId );
543         coordinate.setVersion( version );
544         coordinate.setClassifier( classifier );
545         coordinate.setExtension( packaging );
546         String path = repoManager.getPathForLocalArtifact( getSession().getProjectBuildingRequest(), coordinate );
547         return new File( repoManager.getLocalRepositoryBasedir( getSession().getProjectBuildingRequest() ), path );
548     }
549 
550     /**
551      * Process the supplied pomFile to get groupId, artifactId, version, and packaging
552      * 
553      * @param model The POM to extract missing artifact coordinates from, must not be <code>null</code>.
554      */
555     private void processModel( Model model )
556     {
557         Parent parent = model.getParent();
558 
559         if ( this.groupId == null )
560         {
561             this.groupId = model.getGroupId();
562             if ( this.groupId == null && parent != null )
563             {
564                 this.groupId = parent.getGroupId();
565             }
566         }
567         if ( this.artifactId == null )
568         {
569             this.artifactId = model.getArtifactId();
570         }
571         if ( this.version == null )
572         {
573             this.version = model.getVersion();
574             if ( this.version == null && parent != null )
575             {
576                 this.version = parent.getVersion();
577             }
578         }
579         if ( this.packaging == null )
580         {
581             this.packaging = model.getPackaging();
582         }
583     }
584 
585     /**
586      * Extract the model from the specified POM file.
587      * 
588      * @param pomFile The path of the POM file to parse, must not be <code>null</code>.
589      * @return The model from the POM file, never <code>null</code>.
590      * @throws MojoExecutionException If the file doesn't exist of cannot be read.
591      */
592     Model readModel( File pomFile )
593         throws MojoExecutionException
594     {
595         Reader reader = null;
596         try
597         {
598             reader = ReaderFactory.newXmlReader( pomFile );
599             final Model model = new MavenXpp3Reader().read( reader );
600             reader.close();
601             reader = null;
602             return model;
603         }
604         catch ( FileNotFoundException e )
605         {
606             throw new MojoExecutionException( "POM not found " + pomFile, e );
607         }
608         catch ( IOException e )
609         {
610             throw new MojoExecutionException( "Error reading POM " + pomFile, e );
611         }
612         catch ( XmlPullParserException e )
613         {
614             throw new MojoExecutionException( "Error parsing POM " + pomFile, e );
615         }
616         finally
617         {
618             IOUtil.close( reader );
619         }
620     }
621 
622     /**
623      * Generates a minimal POM from the user-supplied artifact information.
624      * 
625      * @return The path to the generated POM file, never <code>null</code>.
626      * @throws MojoExecutionException If the generation failed.
627      */
628     private File generatePomFile()
629         throws MojoExecutionException
630     {
631         Model model = generateModel();
632 
633         Writer fw = null;
634         try
635         {
636             File tempFile = File.createTempFile( "mvndeploy", ".pom" );
637             tempFile.deleteOnExit();
638 
639             fw = WriterFactory.newXmlWriter( tempFile );
640 
641             new MavenXpp3Writer().write( fw, model );
642 
643             fw.close();
644             fw = null;
645 
646             return tempFile;
647         }
648         catch ( IOException e )
649         {
650             throw new MojoExecutionException( "Error writing temporary pom file: " + e.getMessage(), e );
651         }
652         finally
653         {
654             IOUtil.close( fw );
655         }
656     }
657 
658     /**
659      * Generates a minimal model from the user-supplied artifact information.
660      * 
661      * @return The generated model, never <code>null</code>.
662      */
663     private Model generateModel()
664     {
665         Model model = new Model();
666 
667         model.setModelVersion( "4.0.0" );
668 
669         model.setGroupId( groupId );
670         model.setArtifactId( artifactId );
671         model.setVersion( version );
672         model.setPackaging( packaging );
673 
674         model.setDescription( description );
675 
676         return model;
677     }
678 
679     void setGroupId( String groupId )
680     {
681         this.groupId = groupId;
682     }
683 
684     void setArtifactId( String artifactId )
685     {
686         this.artifactId = artifactId;
687     }
688 
689     void setVersion( String version )
690     {
691         this.version = version;
692     }
693 
694     void setPackaging( String packaging )
695     {
696         this.packaging = packaging;
697     }
698 
699     void setPomFile( File pomFile )
700     {
701         this.pomFile = pomFile;
702     }
703 
704     String getGroupId()
705     {
706         return groupId;
707     }
708 
709     String getArtifactId()
710     {
711         return artifactId;
712     }
713 
714     String getVersion()
715     {
716         return version;
717     }
718 
719     String getPackaging()
720     {
721         return packaging;
722     }
723 
724     File getFile()
725     {
726         return file;
727     }
728 
729     String getClassifier()
730     {
731         return classifier;
732     }
733 
734     void setClassifier( String classifier )
735     {
736         this.classifier = classifier;
737     }
738 
739 }