View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugin.eclipse;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Properties;
28  import java.util.jar.Attributes;
29  import java.util.jar.JarFile;
30  import java.util.jar.Manifest;
31  
32  import org.apache.maven.artifact.Artifact;
33  import org.apache.maven.artifact.repository.ArtifactRepository;
34  import org.apache.maven.plugin.AbstractMojo;
35  import org.apache.maven.plugin.MojoExecutionException;
36  import org.apache.maven.plugin.MojoFailureException;
37  import org.apache.maven.plugin.logging.Log;
38  import org.apache.maven.project.MavenProject;
39  import org.apache.maven.project.MavenProjectBuilder;
40  import org.apache.maven.project.ProjectBuildingException;
41  import org.apache.maven.shared.osgi.Maven2OsgiConverter;
42  import org.codehaus.plexus.archiver.ArchiverException;
43  import org.codehaus.plexus.archiver.UnArchiver;
44  import org.codehaus.plexus.archiver.manager.ArchiverManager;
45  import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
46  import org.codehaus.plexus.components.interactivity.InputHandler;
47  import org.codehaus.plexus.util.FileUtils;
48  
49  /**
50   * Install plugins resolved from the Maven repository system into an Eclipse instance.
51   * 
52   * @goal install-plugins
53   * @author jdcasey
54   * @requiresDependencyResolution compile
55   */
56  public class InstallPluginsMojo
57      extends AbstractMojo
58  {
59  
60      /**
61       * Set this property in a plugin POM's <properties/> section to determine whether that plugin should be
62       * expanded during installation, or left as a jar file.
63       */
64      public static final String PROP_UNPACK_PLUGIN = "eclipse.unpack";
65  
66      /**
67       * This is the installed base directory of the Eclipse instance you want to modify.
68       * 
69       * @parameter expression="${eclipseDir}"
70       */
71      private File eclipseDir;
72  
73      /**
74       * Determines whether this mojo leaves existing installed plugins as-is, or overwrites them.
75       * 
76       * @parameter expression="${overwrite}" default-value="false"
77       */
78      private boolean overwrite;
79  
80      /**
81       * The list of resolved dependencies from the current project. Since we're not resolving the dependencies by hand
82       * here, the build will fail if some of these dependencies do not resolve.
83       * 
84       * @parameter default-value="${project.artifacts}"
85       * @required
86       * @readonly
87       */
88      private Collection artifacts;
89  
90      /**
91       * Comma-delimited list of dependency <type/> values which will be installed in the eclipse instance's plugins
92       * directory.
93       * 
94       * @parameter expression="${pluginDependencyTypes}" default-value="jar"
95       */
96      private String pluginDependencyTypes;
97  
98      /**
99       * The location of the Maven local repository, from which to install resolved dependency plugins.
100      * 
101      * @parameter default-value="${localRepository}"
102      * @required
103      * @readonly
104      */
105     private ArtifactRepository localRepository;
106 
107     /**
108      * Used to retrieve the project metadata (POM) associated with each plugin dependency, to help determine whether
109      * that plugin should be installed as a jar, or expanded into a directory.
110      * 
111      * @component
112      */
113     private MavenProjectBuilder projectBuilder;
114 
115     /**
116      * Used to configure and retrieve an appropriate tool for extracting each resolved plugin dependency. It is
117      * conceivable that some resolved dependencies could be zip files, jar files, or other types, so the manager
118      * approach is a convenient way to provide extensibility here.
119      * 
120      * @component
121      */
122     private ArchiverManager archiverManager;
123 
124     /**
125      * Input handler, needed for comand line handling.
126      * 
127      * @component
128      */
129     private InputHandler inputHandler;
130 
131     // calculated below. Value will be ${eclipseDir}/plugins.
132     private File pluginsDir;
133 
134     /**
135      * @component
136      */
137     private Maven2OsgiConverter maven2OsgiConverter;
138 
139     public InstallPluginsMojo()
140     {
141         // used for plexus init.
142     }
143 
144     // used primarily for testing.
145     protected InstallPluginsMojo( File eclipseDir, boolean overwrite, List dependencyArtifacts,
146                                   String pluginDependencyTypes, ArtifactRepository localRepository,
147                                   MavenProjectBuilder projectBuilder, ArchiverManager archiverManager,
148                                   InputHandler inputHandler, Log log )
149     {
150         this.eclipseDir = eclipseDir;
151         this.overwrite = overwrite;
152         artifacts = dependencyArtifacts;
153         this.pluginDependencyTypes = pluginDependencyTypes;
154         this.localRepository = localRepository;
155         this.projectBuilder = projectBuilder;
156         this.archiverManager = archiverManager;
157         this.inputHandler = inputHandler;
158         setLog( log );
159     }
160 
161     /**
162      * Traverse the list of resolved dependency artifacts. For each one having a type that is listed in the
163      * pluginDependencyTypes parameter value, resolve the associated project metadata (POM), and perform install(..) on
164      * that artifact.
165      */
166     public void execute()
167         throws MojoExecutionException, MojoFailureException
168     {
169         if ( eclipseDir == null )
170         {
171             getLog().info( "Eclipse directory? " );
172 
173             String eclipseDirString;
174             try
175             {
176                 eclipseDirString = inputHandler.readLine();
177             }
178             catch ( IOException e )
179             {
180                 throw new MojoExecutionException( "Unable to read from standard input", e );
181             }
182 
183             eclipseDir = new File( eclipseDirString );
184         }
185 
186         if ( eclipseDir.exists() && !eclipseDir.isDirectory() )
187         {
188             throw new MojoFailureException( "Invalid Eclipse directory: " + eclipseDir );
189         }
190         else if ( !eclipseDir.exists() )
191         {
192             eclipseDir.mkdirs();
193         }
194 
195         for ( Iterator it = artifacts.iterator(); it.hasNext(); )
196         {
197             Artifact artifact = (Artifact) it.next();
198 
199             if ( pluginDependencyTypes.indexOf( artifact.getType() ) > -1 )
200             {
201                 getLog().debug( "Processing Eclipse plugin dependency: " + artifact.getId() );
202 
203                 MavenProject project;
204 
205                 try
206                 {
207                     project =
208                         projectBuilder.buildFromRepository( artifact, Collections.EMPTY_LIST, localRepository, true );
209                 }
210                 catch ( ProjectBuildingException e )
211                 {
212                     throw new MojoExecutionException( "Failed to load project metadata (POM) for: " + artifact.getId(),
213                                                       e );
214                 }
215 
216                 install( artifact, project );
217             }
218             else
219             {
220                 getLog().debug(
221                                 "Skipping dependency: " + artifact.getId() +
222                                     ". Set pluginDependencyTypes with a comma-separated list of types to change this." );
223             }
224         }
225     }
226 
227     /**
228      * <p>
229      * Install the plugin into the eclipse instance's /plugins directory
230      * </p>
231      * <ol>
232      * <li>Determine whether the plugin should be extracted into a directory or not</li>
233      * <li>If the plugin's target location exists, or overwrite is set to true:
234      * <ol type="a">
235      * <li>if extract, ensure the plugin target location exists (mkdirs), and extract there.</li>
236      * <li>copy the plugin file from the local repository to the target location</li>
237      * </ol>
238      * <p>
239      * Warn whenever a plugin will overwrite an existing file or directory, and emit an INFO message whenever a plugin
240      * installation is skipped because of an existing file and overwrite == false.
241      * </p>
242      * 
243      * @param artifact The plugin dependency as it has been resolved.
244      * @param project The project metadata for the accompanying plugin-dependency artifact, used to determine whether to
245      *            install as a jar or as a directory
246      * @throws MojoExecutionException In the event the plugin should be extracted but cannot, or the file copy fails (in
247      *             the event it should not be extracted)
248      * @throws MojoFailureException In the event that the plugins target directory (inside the Eclipse instance
249      *             directory) does not exist, or is not a directory.
250      */
251     private void install( Artifact artifact, MavenProject project )
252         throws MojoExecutionException, MojoFailureException
253     {
254         if ( pluginsDir == null )
255         {
256             pluginsDir = new File( eclipseDir, "plugins" );
257         }
258 
259         if ( !pluginsDir.exists() || !pluginsDir.isDirectory() )
260         {
261             throw new MojoFailureException( "Invalid Eclipse directory: " + eclipseDir +
262                 " (plugins directory is missing or not a directory)." );
263         }
264 
265         boolean installAsJar = true;
266 
267         Properties properties = project.getProperties();
268         if ( properties != null )
269         {
270             installAsJar = !Boolean.valueOf( properties.getProperty( PROP_UNPACK_PLUGIN, "false" ) ).booleanValue();
271         }
272 
273         Attributes attributes = null;
274         try
275         {
276             // don't verify, plugins zipped by eclipse:make-artifacts could have a bad signature
277             JarFile jar = new JarFile( artifact.getFile(), false );
278             Manifest manifest = jar.getManifest();
279             attributes = manifest.getMainAttributes();
280         }
281         catch ( IOException e )
282         {
283             throw new MojoExecutionException( "Unable to read manifest of plugin " +
284                 artifact.getFile().getAbsolutePath(), e );
285         }
286 
287         String bundleVersion = attributes.getValue( "Bundle-Version" );
288         String pluginName = formatEclipsePluginName( artifact, bundleVersion );
289 
290         File pluginFile = new File( pluginsDir, pluginName + ".jar" );
291         File pluginDir = new File( pluginsDir, pluginName );
292 
293         boolean skipped = true;
294 
295         /* check if artifact is an OSGi bundle and ignore if not */
296         Object bundleName = attributes.getValue( "Bundle-Name" );
297         if ( bundleName == null )
298         {
299             getLog().debug(
300                             "Ignoring " + artifact.getArtifactId() +
301                                 " as it is not an OSGi bundle (no Bundle-Name in manifest)" );
302             return;
303         }
304 
305         if ( overwrite )
306         {
307             if ( pluginFile.exists() || pluginDir.exists() )
308             {
309                 getLog().warn( "Overwriting old plugin with contents of: " + artifact.getId() );
310 
311                 getLog().debug( "Removing old plugin from both: " + pluginFile + " and: " + pluginDir );
312 
313                 try
314                 {
315                     FileUtils.forceDelete( pluginDir );
316                     FileUtils.forceDelete( pluginFile );
317                 }
318                 catch ( IOException e )
319                 {
320                     throw new MojoExecutionException( "Failed to remove old plugin from: " + pluginFile + " or: " +
321                         pluginDir, e );
322                 }
323 
324                 getLog().debug( "Removal of old plugin is complete; proceeding with plugin installation." );
325             }
326 
327             performFileOperations( installAsJar, artifact, pluginFile, pluginDir );
328 
329             skipped = false;
330         }
331         else if ( installAsJar && !pluginFile.exists() )
332         {
333             performFileOperations( installAsJar, artifact, pluginFile, pluginDir );
334 
335             skipped = false;
336         }
337         else if ( !installAsJar && !pluginDir.exists() )
338         {
339             performFileOperations( installAsJar, artifact, pluginFile, pluginDir );
340 
341             skipped = false;
342         }
343 
344         if ( skipped )
345         {
346             if ( installAsJar )
347             {
348                 getLog().info(
349                                "Skipping plugin installation for: " + artifact.getId() + "; file: " + pluginFile +
350                                    " already exists. Set overwrite = true to override this." );
351             }
352             else if ( !installAsJar )
353             {
354                 getLog().info(
355                                "Skipping plugin installation for: " + artifact.getId() + "; directory: " + pluginDir +
356                                    " already exists. Set overwrite = true to override this." );
357             }
358         }
359     }
360 
361     private void performFileOperations( boolean installAsJar, Artifact artifact, File pluginFile, File pluginDir )
362         throws MojoExecutionException
363     {
364         File artifactFile = artifact.getFile();
365 
366         if ( installAsJar )
367         {
368             try
369             {
370                 getLog().debug( "Copying: " + artifact.getId() + " to: " + pluginFile );
371 
372                 FileUtils.copyFile( artifactFile, pluginFile );
373             }
374             catch ( IOException e )
375             {
376                 throw new MojoExecutionException( "Failed to copy Eclipse plugin: " + artifact.getId() + "\nfrom: " +
377                     artifact.getFile() + "\nto: " + pluginFile, e );
378             }
379         }
380         else
381         {
382             try
383             {
384                 getLog().debug( "Expanding: " + artifact.getId() + " into: " + pluginDir );
385 
386                 pluginDir.mkdirs();
387 
388                 UnArchiver unarchiver = archiverManager.getUnArchiver( artifactFile );
389 
390                 unarchiver.setSourceFile( artifactFile );
391                 unarchiver.setDestDirectory( pluginDir );
392                 unarchiver.extract();
393             }
394             catch ( NoSuchArchiverException e )
395             {
396                 throw new MojoExecutionException( "Could not find unarchiver for: " + artifactFile, e );
397             }
398             catch ( ArchiverException e )
399             {
400                 throw new MojoExecutionException( "Could not extract: " + artifactFile, e );
401             }
402             catch ( IOException e )
403             {
404                 throw new MojoExecutionException( "Could not extract: " + artifactFile, e );
405             }
406         }
407     }
408 
409     /**
410      * <p>
411      * Format the artifact information into an Eclipse-friendly plugin name. Currently, this is just:
412      * <code>artifactId + "_" + bundle version</code> if bundle version is not null.
413      * </p>
414      */
415     private String formatEclipsePluginName( Artifact artifact, String bundleVersion )
416     {
417         return maven2OsgiConverter.getBundleSymbolicName( artifact ) + "_" +
418             maven2OsgiConverter.getVersion( artifact.getVersion() );
419     }
420 
421 }