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             if ( !Boolean.TRUE.equals( generatePom ) )
210             {
211                 pomFile = temporaryPom;
212                 getLog().debug( "Using JAR embedded POM as pomFile" );
213             }
214         }
215 
216         if ( groupId == null || artifactId == null || version == null || packaging == null )
217         {
218             throw new MojoExecutionException( "The artifact information is incomplete: 'groupId', 'artifactId', "
219                     + "'version' and 'packaging' are required." );
220         }
221 
222         if ( !isValidId( groupId )
223                 || !isValidId( artifactId )
224                 || !isValidVersion( version ) )
225         {
226             throw new MojoExecutionException( "The artifact information is not valid: uses invalid characters." );
227         }
228 
229         InstallRequest installRequest = new InstallRequest();
230 
231         boolean isFilePom = classifier == null && "pom".equals( packaging );
232         if ( !isFilePom )
233         {
234             ArtifactType artifactType = repositorySystemSession.getArtifactTypeRegistry().get( packaging );
235             if ( artifactType != null
236                     && StringUtils.isEmpty( classifier )
237                     && !StringUtils.isEmpty( artifactType.getClassifier() ) )
238             {
239                 classifier = artifactType.getClassifier();
240             }
241         }
242         Artifact mainArtifact = new DefaultArtifact(
243                 groupId,
244                 artifactId,
245                 classifier,
246                 isFilePom ? "pom" : getExtension( file ),
247                 version
248         ).setFile( file );
249         installRequest.addArtifact( mainArtifact );
250 
251         File artifactLocalFile = getLocalRepositoryFile( session.getRepositorySession(), mainArtifact );
252         File pomLocalFile = getPomLocalRepositoryFile( session.getRepositorySession(), mainArtifact );
253 
254         if ( file.equals( artifactLocalFile ) )
255         {
256             throw new MojoFailureException( "Cannot install artifact. "
257                 + "Artifact is already in the local repository." + LS + LS + "File in question is: " + file + LS );
258         }
259 
260         if ( !"pom".equals( packaging ) )
261         {
262             if ( pomFile != null )
263             {
264                 installRequest.addArtifact( new SubArtifact( mainArtifact, "", "pom", pomFile ) );
265             }
266             else
267             {
268                 if ( Boolean.TRUE.equals( generatePom ) || ( generatePom == null && !pomLocalFile.exists() ) )
269                 {
270                     temporaryPom = generatePomFile();
271                     getLog().debug( "Installing generated POM" );
272                     installRequest.addArtifact( new SubArtifact( mainArtifact, "", "pom", temporaryPom ) );
273                 }
274                 else if ( generatePom == null )
275                 {
276                     getLog().debug( "Skipping installation of generated POM, already present in local repository" );
277                 }
278             }
279         }
280 
281         if ( sources != null )
282         {
283             installRequest.addArtifact( new SubArtifact( mainArtifact, "sources", "jar", sources ) );
284         }
285 
286         if ( javadoc != null )
287         {
288             installRequest.addArtifact( new SubArtifact( mainArtifact, "javadoc", "jar", javadoc ) );
289         }
290 
291         try
292         {
293             repositorySystem.install( repositorySystemSession, installRequest );
294         }
295         catch ( InstallationException e )
296         {
297             throw new MojoExecutionException( e.getMessage(), e );
298         }
299         finally
300         {
301             if ( temporaryPom != null )
302             {
303                 // noinspection ResultOfMethodCallIgnored
304                 temporaryPom.delete();
305             }
306         }
307     }
308 
309     private File readingPomFromJarFile()
310         throws MojoExecutionException
311     {
312         File pomFile = null;
313 
314         JarFile jarFile = null;
315         try
316         {
317             Pattern pomEntry = Pattern.compile( "META-INF/maven/.*/pom\\.xml" );
318 
319             jarFile = new JarFile( file );
320 
321             Enumeration<JarEntry> jarEntries = jarFile.entries();
322 
323             while ( jarEntries.hasMoreElements() )
324             {
325                 JarEntry entry = jarEntries.nextElement();
326 
327                 if ( pomEntry.matcher( entry.getName() ).matches() )
328                 {
329                     getLog().debug( "Loading " + entry.getName() );
330 
331                     InputStream pomInputStream = null;
332                     OutputStream pomOutputStream = null;
333 
334                     try
335                     {
336                         pomInputStream = jarFile.getInputStream( entry );
337 
338                         String base = file.getName();
339                         if ( base.indexOf( '.' ) > 0 )
340                         {
341                             base = base.substring( 0, base.lastIndexOf( '.' ) );
342                         }
343                         pomFile = File.createTempFile( base, ".pom" );
344 
345                         pomOutputStream = Files.newOutputStream( pomFile.toPath() );
346 
347                         IOUtil.copy( pomInputStream, pomOutputStream );
348 
349                         pomOutputStream.close();
350                         pomOutputStream = null;
351 
352                         pomInputStream.close();
353                         pomInputStream = null;
354 
355                         processModel( readModel( pomFile ) );
356 
357                         break;
358                     }
359                     finally
360                     {
361                         IOUtil.close( pomInputStream );
362                         IOUtil.close( pomOutputStream );
363                     }
364                 }
365             }
366 
367             if ( pomFile == null )
368             {
369                 getLog().info( "pom.xml not found in " + file.getName() );
370             }
371         }
372         catch ( IOException e )
373         {
374             // ignore, artifact not packaged by Maven
375         }
376         finally
377         {
378             if ( jarFile != null )
379             {
380                 try
381                 {
382                     jarFile.close();
383                 }
384                 catch ( IOException e )
385                 {
386                     // we did our best
387                 }
388             }
389         }
390         return pomFile;
391     }
392 
393     /**
394      * Parses a POM.
395      * 
396      * @param pomFile The path of the POM file to parse, must not be <code>null</code>.
397      * @return The model from the POM file, never <code>null</code>.
398      * @throws MojoExecutionException If the POM could not be parsed.
399      */
400     private Model readModel( File pomFile )
401         throws MojoExecutionException
402     {
403         Reader reader = null;
404         try
405         {
406             reader = new XmlStreamReader( pomFile );
407             final Model model = new MavenXpp3Reader().read( reader );
408             reader.close();
409             reader = null;
410             return model;
411         }
412         catch ( FileNotFoundException e )
413         {
414             throw new MojoExecutionException( "File not found " + pomFile, e );
415         }
416         catch ( IOException e )
417         {
418             throw new MojoExecutionException( "Error reading POM " + pomFile, e );
419         }
420         catch ( XmlPullParserException e )
421         {
422             throw new MojoExecutionException( "Error parsing POM " + pomFile, e );
423         }
424         finally
425         {
426             IOUtil.close( reader );
427         }
428     }
429 
430     /**
431      * Populates missing mojo parameters from the specified POM.
432      * 
433      * @param model The POM to extract missing artifact coordinates from, must not be <code>null</code>.
434      */
435     private void processModel( Model model )
436     {
437         Parent parent = model.getParent();
438 
439         if ( this.groupId == null )
440         {
441             this.groupId = model.getGroupId();
442             if ( this.groupId == null && parent != null )
443             {
444                 this.groupId = parent.getGroupId();
445             }
446         }
447         if ( this.artifactId == null )
448         {
449             this.artifactId = model.getArtifactId();
450         }
451         if ( this.version == null )
452         {
453             this.version = model.getVersion();
454             if ( this.version == null && parent != null )
455             {
456                 this.version = parent.getVersion();
457             }
458         }
459         if ( this.packaging == null )
460         {
461             this.packaging = model.getPackaging();
462         }
463     }
464 
465     /**
466      * Generates a minimal model from the user-supplied artifact information.
467      * 
468      * @return The generated model, never <code>null</code>.
469      */
470     private Model generateModel()
471     {
472         Model model = new Model();
473 
474         model.setModelVersion( "4.0.0" );
475 
476         model.setGroupId( groupId );
477         model.setArtifactId( artifactId );
478         model.setVersion( version );
479         model.setPackaging( packaging );
480 
481         model.setDescription( "POM was created from install:install-file" );
482 
483         return model;
484     }
485 
486     /**
487      * Generates a (temporary) POM file from the plugin configuration. It's the responsibility of the caller to delete
488      * the generated file when no longer needed.
489      * 
490      * @return The path to the generated POM file, never <code>null</code>.
491      * @throws MojoExecutionException If the POM file could not be generated.
492      */
493     private File generatePomFile()
494         throws MojoExecutionException
495     {
496         Model model = generateModel();
497 
498         Writer writer = null;
499         try
500         {
501             File pomFile = File.createTempFile( "mvninstall", ".pom" );
502 
503             writer = new XmlStreamWriter( pomFile );
504             new MavenXpp3Writer().write( writer, model );
505             writer.close();
506             writer = null;
507 
508             return pomFile;
509         }
510         catch ( IOException e )
511         {
512             throw new MojoExecutionException( "Error writing temporary POM file: " + e.getMessage(), e );
513         }
514         finally
515         {
516             IOUtil.close( writer );
517         }
518     }
519 
520     /**
521      * Gets the path of the specified artifact within the local repository. Note that the returned path need not exist
522      * (yet).
523      */
524     private File getLocalRepositoryFile( RepositorySystemSession session, Artifact artifact )
525     {
526         String path = session.getLocalRepositoryManager().getPathForLocalArtifact( artifact );
527         return new File( session.getLocalRepository().getBasedir(), path );
528     }
529 
530     /**
531      * Gets the path of the specified artifact POM within the local repository. Note that the returned path need
532      * not exist (yet).
533      */
534     private File getPomLocalRepositoryFile( RepositorySystemSession session, Artifact artifact )
535     {
536         SubArtifact pomArtifact = new SubArtifact( artifact, "", "pom" );
537         String path = session.getLocalRepositoryManager().getPathForLocalArtifact( pomArtifact );
538         return new File( session.getLocalRepository().getBasedir(), path );
539     }
540 
541     // these below should be shared (duplicated in m-install-p, m-deploy-p)
542 
543     /**
544      * Specialization of {@link FileUtils#getExtension(String)} that honors various {@code tar.xxx} combinations.
545      */
546     private String getExtension( final File file )
547     {
548         String filename = file.getName();
549         if ( filename.contains( ".tar." ) )
550         {
551             return "tar." + FileUtils.getExtension( filename );
552         }
553         else
554         {
555             return FileUtils.getExtension( filename );
556         }
557     }
558 
559     /**
560      * Returns {@code true} if passed in string is "valid Maven ID" (groupId or artifactId).
561      */
562     private boolean isValidId( String id )
563     {
564         if ( id == null )
565         {
566             return false;
567         }
568         for ( int i = 0; i < id.length(); i++ )
569         {
570             char c = id.charAt( i );
571             if ( !( c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'
572                     || c >= '0' && c <= '9' || c == '-' || c == '_' || c == '.' ) )
573             {
574                 return false;
575             }
576         }
577         return true;
578     }
579 
580     private static final String ILLEGAL_VERSION_CHARS = "\\/:\"<>|?*[](){},";
581 
582     /**
583      * Returns {@code true} if passed in string is "valid Maven (simple. non range, expression, etc) version".
584      */
585     private boolean isValidVersion( String version )
586     {
587         if ( version == null )
588         {
589             return false;
590         }
591         for ( int i = version.length() - 1; i >= 0; i-- )
592         {
593             if ( ILLEGAL_VERSION_CHARS.indexOf( version.charAt( i ) ) >= 0 )
594             {
595                 return false;
596             }
597         }
598         return true;
599     }
600 
601 }