View Javadoc
1   package org.apache.maven.plugins.install;
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.InputStream;
26  import java.io.OutputStream;
27  import java.io.Reader;
28  import java.io.Writer;
29  import java.nio.file.Files;
30  import java.util.Enumeration;
31  import java.util.jar.JarEntry;
32  import java.util.jar.JarFile;
33  import java.util.regex.Pattern;
34  
35  import org.apache.maven.execution.MavenSession;
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.AbstractMojo;
41  import org.apache.maven.plugin.MojoExecutionException;
42  import org.apache.maven.plugin.MojoFailureException;
43  import org.apache.maven.plugins.annotations.Component;
44  import org.apache.maven.plugins.annotations.Mojo;
45  import org.apache.maven.plugins.annotations.Parameter;
46  import org.codehaus.plexus.util.FileUtils;
47  import org.codehaus.plexus.util.IOUtil;
48  import org.codehaus.plexus.util.StringUtils;
49  import org.codehaus.plexus.util.xml.XmlStreamReader;
50  import org.codehaus.plexus.util.xml.XmlStreamWriter;
51  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
52  import org.eclipse.aether.DefaultRepositoryCache;
53  import org.eclipse.aether.DefaultRepositorySystemSession;
54  import org.eclipse.aether.RepositorySystem;
55  import org.eclipse.aether.RepositorySystemSession;
56  import org.eclipse.aether.artifact.Artifact;
57  import org.eclipse.aether.artifact.ArtifactType;
58  import org.eclipse.aether.artifact.DefaultArtifact;
59  import org.eclipse.aether.installation.InstallRequest;
60  import org.eclipse.aether.installation.InstallationException;
61  import org.eclipse.aether.repository.LocalRepository;
62  import org.eclipse.aether.repository.LocalRepositoryManager;
63  import org.eclipse.aether.util.artifact.SubArtifact;
64  
65  /**
66   * Installs a file in the local repository.
67   * 
68   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
69   */
70  @Mojo( name = "install-file", requiresProject = false, aggregator = true, threadSafe = true )
71  public class InstallFileMojo
72          extends AbstractMojo
73  {
74      private static final String LS = System.getProperty( "line.separator" );
75  
76      @Component
77      private RepositorySystem repositorySystem;
78  
79      @Parameter( defaultValue = "${session}", required = true, readonly = true )
80      private MavenSession session;
81  
82      /**
83       * GroupId of the artifact to be installed. Retrieved from POM file if one is specified or extracted from
84       * {@code pom.xml} in jar if available.
85       */
86      @Parameter( property = "groupId" )
87      private String groupId;
88  
89      /**
90       * ArtifactId of the artifact to be installed. Retrieved from POM file if one is specified or extracted from
91       * {@code pom.xml} in jar if available.
92       */
93      @Parameter( property = "artifactId" )
94      private String artifactId;
95  
96      /**
97       * Version of the artifact to be installed. Retrieved from POM file if one is specified or extracted from
98       * {@code pom.xml} in jar if available.
99       */
100     @Parameter( property = "version" )
101     private String version;
102 
103     /**
104      * Packaging type of the artifact to be installed. Retrieved from POM file if one is specified or extracted from
105      * {@code pom.xml} in jar if available.
106      */
107     @Parameter( property = "packaging" )
108     private String packaging;
109 
110     /**
111      * Classifier type of the artifact to be installed. For example, "sources" or "javadoc". Defaults to none which
112      * means this is the project's main artifact.
113      * 
114      * @since 2.2
115      */
116     @Parameter( property = "classifier" )
117     private String classifier;
118 
119     /**
120      * The file to be installed in the local repository.
121      */
122     @Parameter( property = "file", required = true )
123     private File file;
124 
125     /**
126      * The bundled API docs for the artifact.
127      * 
128      * @since 2.3
129      */
130     @Parameter( property = "javadoc" )
131     private File javadoc;
132 
133     /**
134      * The bundled sources for the artifact.
135      * 
136      * @since 2.3
137      */
138     @Parameter( property = "sources" )
139     private File sources;
140 
141     /**
142      * Location of an existing POM file to be installed alongside the main artifact, given by the {@link #file}
143      * parameter.
144      * 
145      * @since 2.1
146      */
147     @Parameter( property = "pomFile" )
148     private File pomFile;
149 
150     /**
151      * Generate a minimal POM for the artifact if none is supplied via the parameter {@link #pomFile}. Defaults to
152      * <code>true</code> if there is no existing POM in the local repository yet.
153      * 
154      * @since 2.1
155      */
156     @Parameter( property = "generatePom" )
157     private Boolean generatePom;
158 
159     /**
160      * The path for a specific local repository directory. If not specified the local repository path configured in the
161      * Maven settings will be used.
162      * 
163      * @since 2.2
164      */
165     @Parameter( property = "localRepositoryPath" )
166     private File localRepositoryPath;
167 
168     @Override
169     public void execute()
170         throws MojoExecutionException, MojoFailureException
171     {
172         if ( !file.exists() )
173         {
174             String message = "The specified file '" + file.getPath() + "' does not exist";
175             getLog().error( message );
176             throw new MojoFailureException( message );
177         }
178 
179         RepositorySystemSession repositorySystemSession = session.getRepositorySession();
180         if ( localRepositoryPath != null )
181         {
182             // "clone" repository session and replace localRepository
183             DefaultRepositorySystemSession newSession = new DefaultRepositorySystemSession(
184                     session.getRepositorySession() );
185             // Clear cache, since we're using a new local repository
186             newSession.setCache( new DefaultRepositoryCache() );
187             // keep same repositoryType
188             String contentType = newSession.getLocalRepository().getContentType();
189             if ( "enhanced".equals( contentType ) )
190             {
191                 contentType = "default";
192             }
193             LocalRepositoryManager localRepositoryManager = repositorySystem.newLocalRepositoryManager( newSession,
194                     new LocalRepository( localRepositoryPath, contentType ) );
195             newSession.setLocalRepositoryManager( localRepositoryManager );
196             repositorySystemSession = newSession;
197             getLog().debug( "localRepoPath: " + localRepositoryManager.getRepository().getBasedir() );
198         }
199 
200         File temporaryPom = null;
201 
202         if ( pomFile != null )
203         {
204             processModel( readModel( pomFile ) );
205         }
206         else
207         {
208             temporaryPom = readingPomFromJarFile();
209             pomFile = temporaryPom;
210         }
211 
212         if ( groupId == null || artifactId == null || version == null || packaging == null )
213         {
214             throw new MojoExecutionException( "The artifact information is incomplete: 'groupId', 'artifactId', "
215                     + "'version' and 'packaging' are required." );
216         }
217 
218         if ( !isValidId( groupId )
219                 || !isValidId( artifactId )
220                 || !isValidVersion( version ) )
221         {
222             throw new MojoExecutionException( "The artifact information is not valid: uses invalid characters." );
223         }
224 
225         InstallRequest installRequest = new InstallRequest();
226 
227         boolean isFilePom = classifier == null && "pom".equals( packaging );
228         if ( !isFilePom )
229         {
230             ArtifactType artifactType = repositorySystemSession.getArtifactTypeRegistry().get( packaging );
231             if ( artifactType != null
232                     && StringUtils.isEmpty( classifier )
233                     && !StringUtils.isEmpty( artifactType.getClassifier() ) )
234             {
235                 classifier = artifactType.getClassifier();
236             }
237         }
238         Artifact mainArtifact = new DefaultArtifact(
239                 groupId,
240                 artifactId,
241                 classifier,
242                 isFilePom ? "pom" : getExtension( file ),
243                 version
244         ).setFile( file );
245         installRequest.addArtifact( mainArtifact );
246 
247         File artifactLocalFile = getLocalRepositoryFile( session.getRepositorySession(), mainArtifact );
248         File pomLocalFile = getPomLocalRepositoryFile( session.getRepositorySession(), mainArtifact );
249 
250         if ( file.equals( artifactLocalFile ) )
251         {
252             throw new MojoFailureException( "Cannot install artifact. "
253                 + "Artifact is already in the local repository." + LS + LS + "File in question is: " + file + LS );
254         }
255 
256         if ( !"pom".equals( packaging ) )
257         {
258             if ( pomFile != null )
259             {
260                 installRequest.addArtifact( new SubArtifact( mainArtifact, "", "pom", pomFile ) );
261             }
262             else
263             {
264                 if ( Boolean.TRUE.equals( generatePom ) || ( generatePom == null && !pomLocalFile.exists() ) )
265                 {
266                     temporaryPom = generatePomFile();
267                     getLog().debug( "Installing generated POM" );
268                     installRequest.addArtifact( new SubArtifact( mainArtifact, "", "pom", temporaryPom ) );
269                 }
270                 else if ( generatePom == null )
271                 {
272                     getLog().debug( "Skipping installation of generated POM, already present in local repository" );
273                 }
274             }
275         }
276 
277         if ( sources != null )
278         {
279             installRequest.addArtifact( new SubArtifact( mainArtifact, "sources", "jar", sources ) );
280         }
281 
282         if ( javadoc != null )
283         {
284             installRequest.addArtifact( new SubArtifact( mainArtifact, "javadoc", "jar", javadoc ) );
285         }
286 
287         try
288         {
289             repositorySystem.install( repositorySystemSession, installRequest );
290         }
291         catch ( InstallationException e )
292         {
293             throw new MojoExecutionException( e.getMessage(), e );
294         }
295         finally
296         {
297             if ( temporaryPom != null )
298             {
299                 // noinspection ResultOfMethodCallIgnored
300                 temporaryPom.delete();
301             }
302         }
303     }
304 
305     private File readingPomFromJarFile()
306         throws MojoExecutionException
307     {
308         File pomFile = null;
309 
310         JarFile jarFile = null;
311         try
312         {
313             Pattern pomEntry = Pattern.compile( "META-INF/maven/.*/pom\\.xml" );
314 
315             jarFile = new JarFile( file );
316 
317             Enumeration<JarEntry> jarEntries = jarFile.entries();
318 
319             while ( jarEntries.hasMoreElements() )
320             {
321                 JarEntry entry = jarEntries.nextElement();
322 
323                 if ( pomEntry.matcher( entry.getName() ).matches() )
324                 {
325                     getLog().debug( "Using " + entry.getName() + " as pomFile" );
326 
327                     InputStream pomInputStream = null;
328                     OutputStream pomOutputStream = null;
329 
330                     try
331                     {
332                         pomInputStream = jarFile.getInputStream( entry );
333 
334                         String base = file.getName();
335                         if ( base.indexOf( '.' ) > 0 )
336                         {
337                             base = base.substring( 0, base.lastIndexOf( '.' ) );
338                         }
339                         pomFile = File.createTempFile( base, ".pom" );
340 
341                         pomOutputStream = Files.newOutputStream( pomFile.toPath() );
342 
343                         IOUtil.copy( pomInputStream, pomOutputStream );
344 
345                         pomOutputStream.close();
346                         pomOutputStream = null;
347 
348                         pomInputStream.close();
349                         pomInputStream = null;
350 
351                         processModel( readModel( pomFile ) );
352 
353                         break;
354                     }
355                     finally
356                     {
357                         IOUtil.close( pomInputStream );
358                         IOUtil.close( pomOutputStream );
359                     }
360                 }
361             }
362 
363             if ( pomFile == null )
364             {
365                 getLog().info( "pom.xml not found in " + file.getName() );
366             }
367         }
368         catch ( IOException e )
369         {
370             // ignore, artifact not packaged by Maven
371         }
372         finally
373         {
374             if ( jarFile != null )
375             {
376                 try
377                 {
378                     jarFile.close();
379                 }
380                 catch ( IOException e )
381                 {
382                     // we did our best
383                 }
384             }
385         }
386         return pomFile;
387     }
388 
389     /**
390      * Parses a POM.
391      * 
392      * @param pomFile The path of the POM file to parse, must not be <code>null</code>.
393      * @return The model from the POM file, never <code>null</code>.
394      * @throws MojoExecutionException If the POM could not be parsed.
395      */
396     private Model readModel( File pomFile )
397         throws MojoExecutionException
398     {
399         Reader reader = null;
400         try
401         {
402             reader = new XmlStreamReader( pomFile );
403             final Model model = new MavenXpp3Reader().read( reader );
404             reader.close();
405             reader = null;
406             return model;
407         }
408         catch ( FileNotFoundException e )
409         {
410             throw new MojoExecutionException( "File not found " + pomFile, e );
411         }
412         catch ( IOException e )
413         {
414             throw new MojoExecutionException( "Error reading POM " + pomFile, e );
415         }
416         catch ( XmlPullParserException e )
417         {
418             throw new MojoExecutionException( "Error parsing POM " + pomFile, e );
419         }
420         finally
421         {
422             IOUtil.close( reader );
423         }
424     }
425 
426     /**
427      * Populates missing mojo parameters from the specified POM.
428      * 
429      * @param model The POM to extract missing artifact coordinates from, must not be <code>null</code>.
430      */
431     private void processModel( Model model )
432     {
433         Parent parent = model.getParent();
434 
435         if ( this.groupId == null )
436         {
437             this.groupId = model.getGroupId();
438             if ( this.groupId == null && parent != null )
439             {
440                 this.groupId = parent.getGroupId();
441             }
442         }
443         if ( this.artifactId == null )
444         {
445             this.artifactId = model.getArtifactId();
446         }
447         if ( this.version == null )
448         {
449             this.version = model.getVersion();
450             if ( this.version == null && parent != null )
451             {
452                 this.version = parent.getVersion();
453             }
454         }
455         if ( this.packaging == null )
456         {
457             this.packaging = model.getPackaging();
458         }
459     }
460 
461     /**
462      * Generates a minimal model from the user-supplied artifact information.
463      * 
464      * @return The generated model, never <code>null</code>.
465      */
466     private Model generateModel()
467     {
468         Model model = new Model();
469 
470         model.setModelVersion( "4.0.0" );
471 
472         model.setGroupId( groupId );
473         model.setArtifactId( artifactId );
474         model.setVersion( version );
475         model.setPackaging( packaging );
476 
477         model.setDescription( "POM was created from install:install-file" );
478 
479         return model;
480     }
481 
482     /**
483      * Generates a (temporary) POM file from the plugin configuration. It's the responsibility of the caller to delete
484      * the generated file when no longer needed.
485      * 
486      * @return The path to the generated POM file, never <code>null</code>.
487      * @throws MojoExecutionException If the POM file could not be generated.
488      */
489     private File generatePomFile()
490         throws MojoExecutionException
491     {
492         Model model = generateModel();
493 
494         Writer writer = null;
495         try
496         {
497             File pomFile = File.createTempFile( "mvninstall", ".pom" );
498 
499             writer = new XmlStreamWriter( pomFile );
500             new MavenXpp3Writer().write( writer, model );
501             writer.close();
502             writer = null;
503 
504             return pomFile;
505         }
506         catch ( IOException e )
507         {
508             throw new MojoExecutionException( "Error writing temporary POM file: " + e.getMessage(), e );
509         }
510         finally
511         {
512             IOUtil.close( writer );
513         }
514     }
515 
516     /**
517      * Gets the path of the specified artifact within the local repository. Note that the returned path need not exist
518      * (yet).
519      */
520     private File getLocalRepositoryFile( RepositorySystemSession session, Artifact artifact )
521     {
522         String path = session.getLocalRepositoryManager().getPathForLocalArtifact( artifact );
523         return new File( session.getLocalRepository().getBasedir(), path );
524     }
525 
526     /**
527      * Gets the path of the specified artifact POM within the local repository. Note that the returned path need
528      * not exist (yet).
529      */
530     private File getPomLocalRepositoryFile( RepositorySystemSession session, Artifact artifact )
531     {
532         SubArtifact pomArtifact = new SubArtifact( artifact, "", "pom" );
533         String path = session.getLocalRepositoryManager().getPathForLocalArtifact( pomArtifact );
534         return new File( session.getLocalRepository().getBasedir(), path );
535     }
536 
537     // these below should be shared (duplicated in m-install-p, m-deploy-p)
538 
539     /**
540      * Specialization of {@link FileUtils#getExtension(String)} that honors various {@code tar.xxx} combinations.
541      */
542     private String getExtension( final File file )
543     {
544         String filename = file.getName();
545         if ( filename.contains( ".tar." ) )
546         {
547             return "tar." + FileUtils.getExtension( filename );
548         }
549         else
550         {
551             return FileUtils.getExtension( filename );
552         }
553     }
554 
555     /**
556      * Returns {@code true} if passed in string is "valid Maven ID" (groupId or artifactId).
557      */
558     private boolean isValidId( String id )
559     {
560         if ( id == null )
561         {
562             return false;
563         }
564         for ( int i = 0; i < id.length(); i++ )
565         {
566             char c = id.charAt( i );
567             if ( !( c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'
568                     || c >= '0' && c <= '9' || c == '-' || c == '_' || c == '.' ) )
569             {
570                 return false;
571             }
572         }
573         return true;
574     }
575 
576     private static final String ILLEGAL_VERSION_CHARS = "\\/:\"<>|?*[](){},";
577 
578     /**
579      * Returns {@code true} if passed in string is "valid Maven (simple. non range, expression, etc) version".
580      */
581     private boolean isValidVersion( String version )
582     {
583         if ( version == null )
584         {
585             return false;
586         }
587         for ( int i = version.length() - 1; i >= 0; i-- )
588         {
589             if ( ILLEGAL_VERSION_CHARS.indexOf( version.charAt( i ) ) >= 0 )
590             {
591                 return false;
592             }
593         }
594         return true;
595     }
596 
597 }