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.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.jar.JarEntry;
32  import java.util.jar.JarFile;
33  import java.util.regex.Pattern;
34  
35  import org.apache.maven.artifact.Artifact;
36  import org.apache.maven.artifact.handler.DefaultArtifactHandler;
37  import org.apache.maven.model.Model;
38  import org.apache.maven.model.Parent;
39  import org.apache.maven.model.building.ModelBuildingException;
40  import org.apache.maven.model.building.ModelSource;
41  import org.apache.maven.model.building.StringModelSource;
42  import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
43  import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
44  import org.apache.maven.plugin.MojoExecutionException;
45  import org.apache.maven.plugin.MojoFailureException;
46  import org.apache.maven.plugins.annotations.Component;
47  import org.apache.maven.plugins.annotations.Mojo;
48  import org.apache.maven.plugins.annotations.Parameter;
49  import org.apache.maven.project.DefaultProjectBuildingRequest;
50  import org.apache.maven.project.MavenProject;
51  import org.apache.maven.project.MavenProjectHelper;
52  import org.apache.maven.project.ProjectBuilder;
53  import org.apache.maven.project.ProjectBuildingException;
54  import org.apache.maven.project.ProjectBuildingRequest;
55  import org.apache.maven.project.artifact.ProjectArtifactMetadata;
56  import org.apache.maven.shared.transfer.project.install.ProjectInstaller;
57  import org.apache.maven.shared.transfer.project.install.ProjectInstallerRequest;
58  import org.apache.maven.shared.utils.Os;
59  import org.apache.maven.shared.utils.ReaderFactory;
60  import org.apache.maven.shared.utils.WriterFactory;
61  import org.apache.maven.shared.utils.io.IOUtil;
62  import org.codehaus.plexus.util.FileUtils;
63  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
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 AbstractInstallMojo
73  {
74  
75      /**
76       * GroupId of the artifact to be installed. Retrieved from POM file if one is specified or extracted from
77       * {@code pom.xml} in jar if available.
78       */
79      @Parameter( property = "groupId" )
80      private String groupId;
81  
82      /**
83       * ArtifactId 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 = "artifactId" )
87      private String artifactId;
88  
89      /**
90       * Version 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 = "version" )
94      private String version;
95  
96      /**
97       * Packaging type 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 = "packaging" )
101     private String packaging;
102 
103     /**
104      * Classifier type of the artifact to be installed. For example, "sources" or "javadoc". Defaults to none which
105      * means this is the project's main artifact.
106      * 
107      * @since 2.2
108      */
109     @Parameter( property = "classifier" )
110     private String classifier;
111 
112     /**
113      * The file to be installed in the local repository.
114      */
115     @Parameter( property = "file", required = true )
116     private File file;
117 
118     /**
119      * The bundled API docs for the artifact.
120      * 
121      * @since 2.3
122      */
123     @Parameter( property = "javadoc" )
124     private File javadoc;
125 
126     /**
127      * The bundled sources for the artifact.
128      * 
129      * @since 2.3
130      */
131     @Parameter( property = "sources" )
132     private File sources;
133 
134     /**
135      * Location of an existing POM file to be installed alongside the main artifact, given by the {@link #file}
136      * parameter.
137      * 
138      * @since 2.1
139      */
140     @Parameter( property = "pomFile" )
141     private File pomFile;
142 
143     /**
144      * Generate a minimal POM for the artifact if none is supplied via the parameter {@link #pomFile}. Defaults to
145      * <code>true</code> if there is no existing POM in the local repository yet.
146      * 
147      * @since 2.1
148      */
149     @Parameter( property = "generatePom" )
150     private Boolean generatePom;
151 
152     /**
153      * The path for a specific local repository directory. If not specified the local repository path configured in the
154      * Maven settings will be used.
155      * 
156      * @since 2.2
157      */
158     @Parameter( property = "localRepositoryPath" )
159     private File localRepositoryPath;
160 
161     /**
162      * Used for attaching the artifacts to install to the project.
163      */
164     @Component
165     private MavenProjectHelper projectHelper;
166 
167     /**
168      * Used for creating the project to which the artifacts to install will be attached.
169      */
170     @Component
171     private ProjectBuilder projectBuilder;
172 
173     /**
174      * Used to install the project created.
175      */
176     @Component
177     private ProjectInstaller installer;
178 
179     /**
180      * @see org.apache.maven.plugin.Mojo#execute()
181      */
182     public void execute()
183         throws MojoExecutionException, MojoFailureException
184     {
185 
186         if ( !file.exists() )
187         {
188             String message = "The specified file '" + file.getPath() + "' not exists";
189             getLog().error( message );
190             throw new MojoFailureException( message );
191         }
192 
193         ProjectBuildingRequest buildingRequest = session.getProjectBuildingRequest();
194 
195         // ----------------------------------------------------------------------
196         // Override the default localRepository variable
197         // ----------------------------------------------------------------------
198         if ( localRepositoryPath != null )
199         {
200             buildingRequest = repositoryManager.setLocalRepositoryBasedir( buildingRequest, localRepositoryPath );
201 
202             getLog().debug( "localRepoPath: " + repositoryManager.getLocalRepositoryBasedir( buildingRequest ) );
203         }
204 
205         File temporaryPom = null;
206 
207         if ( pomFile != null )
208         {
209             processModel( readModel( pomFile ) );
210         }
211         else
212         {
213             temporaryPom = readingPomFromJarFile();
214             pomFile = temporaryPom;
215         }
216 
217         MavenProject project = createMavenProject();
218         
219         // We need to set a new ArtifactHandler otherwise 
220         // the extension will be set to the packaging type
221         // which is sometimes wrong.
222         DefaultArtifactHandler ah = new DefaultArtifactHandler( packaging );
223         ah.setExtension( FileUtils.getExtension( file.getName() ) );
224 
225         project.getArtifact().setArtifactHandler( ah );
226         Artifact artifact = project.getArtifact();
227 
228         if ( file.equals( getLocalRepoFile( buildingRequest, artifact ) ) )
229         {
230             throw new MojoFailureException( "Cannot install artifact. "
231                 + "Artifact is already in the local repository.\n\nFile in question is: " + file + "\n" );
232         }
233 
234         if ( classifier == null )
235         {
236             artifact.setFile( file );
237             if ( "pom".equals( packaging ) )
238             {
239                 project.setFile( file );
240             }
241         }
242         else
243         {
244             projectHelper.attachArtifact( project, packaging, classifier, file );
245         }
246 
247         if ( !"pom".equals( packaging ) )
248         {
249             if ( pomFile != null )
250             {
251                 if ( classifier == null )
252                 {
253                     artifact.addMetadata( new ProjectArtifactMetadata( artifact, pomFile ) );
254                 }
255                 else
256                 {
257                     project.setFile( pomFile );
258                 }
259             }
260             else
261             {
262                 temporaryPom = generatePomFile();
263                 ProjectArtifactMetadata pomMetadata = new ProjectArtifactMetadata( artifact, temporaryPom );
264                 if ( Boolean.TRUE.equals( generatePom )
265                     || ( generatePom == null && !getLocalRepoFile( buildingRequest, pomMetadata ).exists() ) )
266                 {
267                     getLog().debug( "Installing generated POM" );
268                     if ( classifier == null )
269                     {
270                         artifact.addMetadata( pomMetadata );
271                     }
272                     else
273                     {
274                         project.setFile( temporaryPom );
275                     }
276                 }
277                 else if ( generatePom == null )
278                 {
279                     getLog().debug( "Skipping installation of generated POM, already present in local repository" );
280                 }
281             }
282         }
283 
284         if ( sources != null )
285         {
286             projectHelper.attachArtifact( project, "jar", "sources", sources );
287         }
288 
289         if ( javadoc != null )
290         {
291             projectHelper.attachArtifact( project, "jar", "javadoc", javadoc );
292         }
293 
294         try
295         {
296             // CHECKSTYLE_OFF: LineLength
297             ProjectInstallerRequest projectInstallerRequest =
298                 new ProjectInstallerRequest().setProject( project );
299             // CHECKSTYLE_ON: LineLength
300 
301             installer.install( buildingRequest, projectInstallerRequest );
302         }
303         catch ( Exception e )
304         {
305             throw new MojoExecutionException( e.getMessage(), e );
306         }
307         finally
308         {
309             if ( temporaryPom != null )
310             {
311                 // noinspection ResultOfMethodCallIgnored
312                 temporaryPom.delete();
313             }
314         }
315     }
316 
317     /**
318      * Creates a Maven project in-memory from the user-supplied groupId, artifactId and version. When a classifier is
319      * supplied, the packaging must be POM because the project with only have attachments. This project serves as basis
320      * to attach the artifacts to install to.
321      * 
322      * @return The created Maven project, never <code>null</code>.
323      * @throws MojoExecutionException When the model of the project could not be built.
324      * @throws MojoFailureException When building the project failed.
325      */
326     private MavenProject createMavenProject()
327         throws MojoExecutionException, MojoFailureException
328     {
329         if ( groupId == null || artifactId == null || version == null || packaging == null )
330         {
331             throw new MojoExecutionException( "The artifact information is incomplete: 'groupId', 'artifactId', "
332                 + "'version' and 'packaging' are required." );
333         }
334         ModelSource modelSource = new StringModelSource( "<project><modelVersion>4.0.0</modelVersion><groupId>"
335             + groupId + "</groupId><artifactId>" + artifactId + "</artifactId><version>" + version
336             + "</version><packaging>" + ( classifier == null ? packaging : "pom" ) + "</packaging></project>" );
337         ProjectBuildingRequest pbr = new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() );
338         pbr.setProcessPlugins( false );
339         try
340         {
341             return projectBuilder.build( modelSource, pbr ).getProject();
342         }
343         catch ( ProjectBuildingException e )
344         {
345             if ( e.getCause() instanceof ModelBuildingException )
346             {
347                 throw new MojoExecutionException( "The artifact information is not valid:" + Os.LINE_SEP
348                     + e.getCause().getMessage() );
349             }
350             throw new MojoFailureException( "Unable to create the project.", e );
351         }
352     }
353 
354     private File readingPomFromJarFile()
355         throws MojoExecutionException
356     {
357         File pomFile = null;
358 
359         JarFile jarFile = null;
360         try
361         {
362             Pattern pomEntry = Pattern.compile( "META-INF/maven/.*/pom\\.xml" );
363 
364             jarFile = new JarFile( file );
365 
366             Enumeration<JarEntry> jarEntries = jarFile.entries();
367 
368             while ( jarEntries.hasMoreElements() )
369             {
370                 JarEntry entry = jarEntries.nextElement();
371 
372                 if ( pomEntry.matcher( entry.getName() ).matches() )
373                 {
374                     getLog().debug( "Using " + entry.getName() + " as pomFile" );
375 
376                     InputStream pomInputStream = null;
377                     OutputStream pomOutputStream = null;
378 
379                     try
380                     {
381                         pomInputStream = jarFile.getInputStream( entry );
382 
383                         String base = file.getName();
384                         if ( base.indexOf( '.' ) > 0 )
385                         {
386                             base = base.substring( 0, base.lastIndexOf( '.' ) );
387                         }
388                         pomFile = File.createTempFile( base, ".pom" );
389 
390                         pomOutputStream = new FileOutputStream( pomFile );
391 
392                         IOUtil.copy( pomInputStream, pomOutputStream );
393 
394                         pomOutputStream.close();
395                         pomOutputStream = null;
396 
397                         pomInputStream.close();
398                         pomInputStream = null;
399 
400                         processModel( readModel( pomFile ) );
401 
402                         break;
403                     }
404                     finally
405                     {
406                         IOUtil.close( pomInputStream );
407                         IOUtil.close( pomOutputStream );
408                     }
409                 }
410             }
411 
412             if ( pomFile == null )
413             {
414                 getLog().info( "pom.xml not found in " + file.getName() );
415             }
416         }
417         catch ( IOException e )
418         {
419             // ignore, artifact not packaged by Maven
420         }
421         finally
422         {
423             if ( jarFile != null )
424             {
425                 try
426                 {
427                     jarFile.close();
428                 }
429                 catch ( IOException e )
430                 {
431                     // we did our best
432                 }
433             }
434         }
435         return pomFile;
436     }
437 
438     /**
439      * Parses a POM.
440      * 
441      * @param pomFile The path of the POM file to parse, must not be <code>null</code>.
442      * @return The model from the POM file, never <code>null</code>.
443      * @throws MojoExecutionException If the POM could not be parsed.
444      */
445     private Model readModel( File pomFile )
446         throws MojoExecutionException
447     {
448         Reader reader = null;
449         try
450         {
451             reader = ReaderFactory.newXmlReader( pomFile );
452             final Model model = new MavenXpp3Reader().read( reader );
453             reader.close();
454             reader = null;
455             return model;
456         }
457         catch ( FileNotFoundException e )
458         {
459             throw new MojoExecutionException( "File not found " + pomFile, e );
460         }
461         catch ( IOException e )
462         {
463             throw new MojoExecutionException( "Error reading POM " + pomFile, e );
464         }
465         catch ( XmlPullParserException e )
466         {
467             throw new MojoExecutionException( "Error parsing POM " + pomFile, e );
468         }
469         finally
470         {
471             IOUtil.close( reader );
472         }
473     }
474 
475     /**
476      * Populates missing mojo parameters from the specified POM.
477      * 
478      * @param model The POM to extract missing artifact coordinates from, must not be <code>null</code>.
479      */
480     private void processModel( Model model )
481     {
482         Parent parent = model.getParent();
483 
484         if ( this.groupId == null )
485         {
486             this.groupId = model.getGroupId();
487             if ( this.groupId == null && parent != null )
488             {
489                 this.groupId = parent.getGroupId();
490             }
491         }
492         if ( this.artifactId == null )
493         {
494             this.artifactId = model.getArtifactId();
495         }
496         if ( this.version == null )
497         {
498             this.version = model.getVersion();
499             if ( this.version == null && parent != null )
500             {
501                 this.version = parent.getVersion();
502             }
503         }
504         if ( this.packaging == null )
505         {
506             this.packaging = model.getPackaging();
507         }
508     }
509 
510     /**
511      * Generates a minimal model from the user-supplied artifact information.
512      * 
513      * @return The generated model, never <code>null</code>.
514      */
515     private Model generateModel()
516     {
517         Model model = new Model();
518 
519         model.setModelVersion( "4.0.0" );
520 
521         model.setGroupId( groupId );
522         model.setArtifactId( artifactId );
523         model.setVersion( version );
524         model.setPackaging( packaging );
525 
526         model.setDescription( "POM was created from install:install-file" );
527 
528         return model;
529     }
530 
531     /**
532      * Generates a (temporary) POM file from the plugin configuration. It's the responsibility of the caller to delete
533      * the generated file when no longer needed.
534      * 
535      * @return The path to the generated POM file, never <code>null</code>.
536      * @throws MojoExecutionException If the POM file could not be generated.
537      */
538     private File generatePomFile()
539         throws MojoExecutionException
540     {
541         Model model = generateModel();
542 
543         Writer writer = null;
544         try
545         {
546             File pomFile = File.createTempFile( "mvninstall", ".pom" );
547 
548             writer = WriterFactory.newXmlWriter( pomFile );
549             new MavenXpp3Writer().write( writer, model );
550             writer.close();
551             writer = null;
552 
553             return pomFile;
554         }
555         catch ( IOException e )
556         {
557             throw new MojoExecutionException( "Error writing temporary POM file: " + e.getMessage(), e );
558         }
559         finally
560         {
561             IOUtil.close( writer );
562         }
563     }
564 
565 }