View Javadoc
1   package org.apache.maven.plugin.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.Reader;
27  import java.io.Writer;
28  import java.net.MalformedURLException;
29  import java.util.Collection;
30  import java.util.Enumeration;
31  import java.util.LinkedHashSet;
32  import java.util.Map;
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.installer.ArtifactInstallationException;
39  import org.apache.maven.artifact.metadata.ArtifactMetadata;
40  import org.apache.maven.artifact.repository.DefaultArtifactRepository;
41  import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
42  import org.apache.maven.model.Model;
43  import org.apache.maven.model.Parent;
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.artifact.ProjectArtifactMetadata;
52  import org.apache.maven.project.validation.ModelValidationResult;
53  import org.apache.maven.project.validation.ModelValidator;
54  import org.apache.maven.shared.utils.io.IOUtil;
55  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
56  import org.apache.maven.shared.utils.ReaderFactory;
57  import org.apache.maven.shared.utils.WriterFactory;
58  
59  /**
60   * Installs a file in the local repository.
61   * 
62   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
63   * @version $Id: InstallFileMojo.java 1617120 2014-08-10 15:15:48Z khmarbaise $
64   */
65  @Mojo( name = "install-file", requiresProject = false, aggregator = true, threadSafe = true )
66  public class InstallFileMojo
67      extends AbstractInstallMojo
68  {
69  
70      /**
71       * GroupId of the artifact to be installed. Retrieved from POM file if one is specified or extracted from
72       * {@code pom.xml} in jar if available.
73       */
74      @Parameter( property = "groupId" )
75      protected String groupId;
76  
77      /**
78       * ArtifactId of the artifact to be installed. Retrieved from POM file if one is specified or extracted from
79       * {@code pom.xml} in jar if available.
80       */
81      @Parameter( property = "artifactId" )
82      protected String artifactId;
83  
84      /**
85       * Version of the artifact to be installed. Retrieved from POM file if one is specified or extracted from
86       * {@code pom.xml} in jar if available.
87       */
88      @Parameter( property = "version" )
89      protected String version;
90  
91      /**
92       * Packaging type of the artifact to be installed. Retrieved from POM file if one is specified or extracted from
93       * {@code pom.xml} in jar if available.
94       */
95      @Parameter( property = "packaging" )
96      protected String packaging;
97  
98      /**
99       * Classifier type of the artifact to be installed. For example, "sources" or "javadoc". Defaults to none which
100      * means this is the project's main artifact.
101      * 
102      * @since 2.2
103      */
104     @Parameter( property = "classifier" )
105     protected String classifier;
106 
107     /**
108      * The file to be installed in the local repository.
109      */
110     @Parameter( property = "file", required = true )
111     private File file;
112 
113     /**
114      * The bundled API docs for the artifact.
115      * 
116      * @since 2.3
117      */
118     @Parameter( property = "javadoc" )
119     private File javadoc;
120 
121     /**
122      * The bundled sources for the artifact.
123      * 
124      * @since 2.3
125      */
126     @Parameter( property = "sources" )
127     private File sources;
128 
129     /**
130      * Location of an existing POM file to be installed alongside the main artifact, given by the {@link #file}
131      * parameter.
132      * 
133      * @since 2.1
134      */
135     @Parameter( property = "pomFile" )
136     private File pomFile;
137 
138     /**
139      * Generate a minimal POM for the artifact if none is supplied via the parameter {@link #pomFile}. Defaults to
140      * <code>true</code> if there is no existing POM in the local repository yet.
141      * 
142      * @since 2.1
143      */
144     @Parameter( property = "generatePom" )
145     private Boolean generatePom;
146 
147     /**
148      * The type of remote repository layout to install to. Try <code>legacy</code> for a Maven 1.x-style repository
149      * layout.
150      * 
151      * @since 2.2
152      */
153     @Parameter( property = "repositoryLayout", defaultValue = "default", required = true )
154     private String repositoryLayout;
155 
156     /**
157      * Map that contains the repository layouts.
158      */
159     @Component( role = ArtifactRepositoryLayout.class )
160     private Map<String, ArtifactRepositoryLayout> repositoryLayouts;
161 
162     /**
163      * The path for a specific local repository directory. If not specified the local repository path configured in the
164      * Maven settings will be used.
165      * 
166      * @since 2.2
167      */
168     @Parameter( property = "localRepositoryPath" )
169     private File localRepositoryPath;
170 
171     /**
172      * The component used to validate the user-supplied artifact coordinates.
173      */
174     @Component
175     private ModelValidator modelValidator;
176 
177     /**
178      * @see org.apache.maven.plugin.Mojo#execute()
179      */
180     public void execute()
181         throws MojoExecutionException, MojoFailureException
182     {
183 
184         if ( !file.exists() )
185         {
186             String message = "The specified file '" + file.getPath() + "' not exists";
187             getLog().error( message );
188             throw new MojoFailureException( message );
189         }
190 
191         // ----------------------------------------------------------------------
192         // Override the default localRepository variable
193         // ----------------------------------------------------------------------
194         if ( localRepositoryPath != null )
195         {
196             try
197             {
198                 ArtifactRepositoryLayout layout = repositoryLayouts.get( repositoryLayout );
199                 getLog().debug( "Layout: " + layout.getClass() );
200 
201                 // noinspection deprecation
202                 localRepository =
203                     new DefaultArtifactRepository( localRepository.getId(), localRepositoryPath.toURL().toString(),
204                                                    layout );
205             }
206             catch ( MalformedURLException e )
207             {
208                 throw new MojoExecutionException( "MalformedURLException: " + e.getMessage(), e );
209             }
210         }
211 
212         if ( pomFile != null )
213         {
214             processModel( readModel( pomFile ) );
215         }
216         else
217         {
218             boolean foundPom = false;
219 
220             try
221             {
222                 Pattern pomEntry = Pattern.compile( "META-INF/maven/.*/pom\\.xml" );
223 
224                 JarFile jarFile = new JarFile( file );
225 
226                 Enumeration<JarEntry> jarEntries = jarFile.entries();
227 
228                 while ( jarEntries.hasMoreElements() )
229                 {
230                     JarEntry entry = jarEntries.nextElement();
231 
232                     if ( pomEntry.matcher( entry.getName() ).matches() )
233                     {
234                         getLog().debug( "Using " + entry.getName() + " for groupId, artifactId, packaging and version" );
235 
236                         foundPom = true;
237 
238                         InputStream pomInputStream = null;
239 
240                         try
241                         {
242                             pomInputStream = jarFile.getInputStream( entry );
243 
244                             processModel( readModel( pomInputStream ) );
245 
246                             break;
247                         }
248                         finally
249                         {
250                             if ( pomInputStream != null )
251                             {
252                                 pomInputStream.close();
253                             }
254                         }
255                     }
256                 }
257 
258                 if ( !foundPom )
259                 {
260                     getLog().info( "pom.xml not found in " + file.getName() );
261                 }
262             }
263             catch ( IOException e )
264             {
265                 // ignore, artifact not packaged by Maven
266             }
267 
268         }
269 
270         validateArtifactInformation();
271 
272         Artifact artifact =
273             artifactFactory.createArtifactWithClassifier( groupId, artifactId, version, packaging, classifier );
274 
275         if ( file.equals( getLocalRepoFile( artifact ) ) )
276         {
277             throw new MojoFailureException( "Cannot install artifact. "
278                 + "Artifact is already in the local repository.\n\nFile in question is: " + file + "\n" );
279         }
280 
281         File generatedPomFile = null;
282 
283         if ( !"pom".equals( packaging ) )
284         {
285             if ( pomFile != null )
286             {
287                 ArtifactMetadata pomMetadata = new ProjectArtifactMetadata( artifact, pomFile );
288                 artifact.addMetadata( pomMetadata );
289             }
290             else
291             {
292                 generatedPomFile = generatePomFile();
293                 ArtifactMetadata pomMetadata = new ProjectArtifactMetadata( artifact, generatedPomFile );
294                 if ( Boolean.TRUE.equals( generatePom )
295                     || ( generatePom == null && !getLocalRepoFile( pomMetadata ).exists() ) )
296                 {
297                     getLog().debug( "Installing generated POM" );
298                     artifact.addMetadata( pomMetadata );
299                 }
300                 else if ( generatePom == null )
301                 {
302                     getLog().debug( "Skipping installation of generated POM, already present in local repository" );
303                 }
304             }
305         }
306 
307         if ( updateReleaseInfo )
308         {
309             artifact.setRelease( true );
310         }
311 
312         Collection<File> metadataFiles = new LinkedHashSet<File>();
313 
314         // TODO: maybe not strictly correct, while we should enforce that packaging has a type handler of the same id,
315         // we don't
316         try
317         {
318             installer.install( file, artifact, localRepository );
319             installChecksums( artifact, createChecksum );
320             addMetaDataFilesForArtifact( artifact, metadataFiles, createChecksum );
321 
322         }
323         catch ( ArtifactInstallationException e )
324         {
325             throw new MojoExecutionException( "Error installing artifact '" + artifact.getDependencyConflictId()
326                 + "': " + e.getMessage(), e );
327         }
328         finally
329         {
330             if ( generatedPomFile != null )
331             {
332                 // noinspection ResultOfMethodCallIgnored
333                 generatedPomFile.delete();
334             }
335         }
336 
337         if ( sources != null )
338         {
339             artifact = artifactFactory.createArtifactWithClassifier( groupId, artifactId, version, "jar", "sources" );
340             try
341             {
342                 installer.install( sources, artifact, localRepository );
343                 installChecksums( artifact, createChecksum );
344                 addMetaDataFilesForArtifact( artifact, metadataFiles, createChecksum );
345 
346             }
347             catch ( ArtifactInstallationException e )
348             {
349                 throw new MojoExecutionException( "Error installing sources " + sources + ": " + e.getMessage(), e );
350             }
351         }
352 
353         if ( javadoc != null )
354         {
355             artifact = artifactFactory.createArtifactWithClassifier( groupId, artifactId, version, "jar", "javadoc" );
356             try
357             {
358                 installer.install( javadoc, artifact, localRepository );
359                 installChecksums( artifact, createChecksum );
360                 addMetaDataFilesForArtifact( artifact, metadataFiles, createChecksum );
361 
362             }
363             catch ( ArtifactInstallationException e )
364             {
365                 throw new MojoExecutionException( "Error installing API docs " + javadoc + ": " + e.getMessage(), e );
366             }
367         }
368 
369         installChecksums( metadataFiles );
370     }
371 
372     /**
373      * Parses a POM.
374      * 
375      * @param pomFile The path of the POM file to parse, must not be <code>null</code>.
376      * @return The model from the POM file, never <code>null</code>.
377      * @throws MojoExecutionException If the POM could not be parsed.
378      */
379     private Model readModel( File pomFile )
380         throws MojoExecutionException
381     {
382         Reader reader = null;
383         try
384         {
385             reader = ReaderFactory.newXmlReader( pomFile );
386             return new MavenXpp3Reader().read( reader );
387         }
388         catch ( FileNotFoundException e )
389         {
390             throw new MojoExecutionException( "File not found " + pomFile, e );
391         }
392         catch ( IOException e )
393         {
394             throw new MojoExecutionException( "Error reading POM " + pomFile, e );
395         }
396         catch ( XmlPullParserException e )
397         {
398             throw new MojoExecutionException( "Error parsing POM " + pomFile, e );
399         }
400         finally
401         {
402             IOUtil.close( reader );
403         }
404     }
405 
406     /**
407      * Parses a POM.
408      * 
409      * @param pomFile The path of the POM file to parse, must not be <code>null</code>.
410      * @return The model from the POM file, never <code>null</code>.
411      * @throws MojoExecutionException If the POM could not be parsed.
412      */
413     private Model readModel( InputStream pomFile )
414         throws MojoExecutionException
415     {
416         Reader reader = null;
417         try
418         {
419             reader = ReaderFactory.newXmlReader( pomFile );
420             return new MavenXpp3Reader().read( reader );
421         }
422         catch ( FileNotFoundException e )
423         {
424             throw new MojoExecutionException( "File not found " + pomFile, e );
425         }
426         catch ( IOException e )
427         {
428             throw new MojoExecutionException( "Error reading POM " + pomFile, e );
429         }
430         catch ( XmlPullParserException e )
431         {
432             throw new MojoExecutionException( "Error parsing POM " + pomFile, e );
433         }
434         finally
435         {
436             IOUtil.close( reader );
437         }
438     }
439 
440     /**
441      * Populates missing mojo parameters from the specified POM.
442      * 
443      * @param model The POM to extract missing artifact coordinates from, must not be <code>null</code>.
444      */
445     private void processModel( Model model )
446     {
447         Parent parent = model.getParent();
448 
449         if ( this.groupId == null )
450         {
451             this.groupId = model.getGroupId();
452             if ( this.groupId == null && parent != null )
453             {
454                 this.groupId = parent.getGroupId();
455             }
456         }
457         if ( this.artifactId == null )
458         {
459             this.artifactId = model.getArtifactId();
460         }
461         if ( this.version == null )
462         {
463             this.version = model.getVersion();
464             if ( this.version == null && parent != null )
465             {
466                 this.version = parent.getVersion();
467             }
468         }
469         if ( this.packaging == null )
470         {
471             this.packaging = model.getPackaging();
472         }
473     }
474 
475     /**
476      * Validates the user-supplied artifact information.
477      * 
478      * @throws MojoExecutionException If any artifact coordinate is invalid.
479      */
480     private void validateArtifactInformation()
481         throws MojoExecutionException
482     {
483         Model model = generateModel();
484 
485         ModelValidationResult result = modelValidator.validate( model );
486 
487         if ( result.getMessageCount() > 0 )
488         {
489             throw new MojoExecutionException( "The artifact information is incomplete or not valid:\n"
490                 + result.render( "  " ) );
491         }
492     }
493 
494     /**
495      * Generates a minimal model from the user-supplied artifact information.
496      * 
497      * @return The generated model, never <code>null</code>.
498      */
499     private Model generateModel()
500     {
501         Model model = new Model();
502 
503         model.setModelVersion( "4.0.0" );
504 
505         model.setGroupId( groupId );
506         model.setArtifactId( artifactId );
507         model.setVersion( version );
508         model.setPackaging( packaging );
509 
510         model.setDescription( "POM was created from install:install-file" );
511 
512         return model;
513     }
514 
515     /**
516      * Generates a (temporary) POM file from the plugin configuration. It's the responsibility of the caller to delete
517      * the generated file when no longer needed.
518      * 
519      * @return The path to the generated POM file, never <code>null</code>.
520      * @throws MojoExecutionException If the POM file could not be generated.
521      */
522     private File generatePomFile()
523         throws MojoExecutionException
524     {
525         Model model = generateModel();
526 
527         Writer writer = null;
528         try
529         {
530             File pomFile = File.createTempFile( "mvninstall", ".pom" );
531 
532             writer = WriterFactory.newXmlWriter( pomFile );
533             new MavenXpp3Writer().write( writer, model );
534 
535             return pomFile;
536         }
537         catch ( IOException e )
538         {
539             throw new MojoExecutionException( "Error writing temporary POM file: " + e.getMessage(), e );
540         }
541         finally
542         {
543             IOUtil.close( writer );
544         }
545     }
546 
547     /**
548      * @return the localRepositoryPath
549      */
550     @SuppressWarnings( "UnusedDeclaration" )
551     public File getLocalRepositoryPath()
552     {
553         return this.localRepositoryPath;
554     }
555 
556     /**
557      * @param theLocalRepositoryPath the localRepositoryPath to set
558      */
559     public void setLocalRepositoryPath( File theLocalRepositoryPath )
560     {
561         this.localRepositoryPath = theLocalRepositoryPath;
562     }
563 
564 }