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