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.codehaus.plexus.archiver.ArchiverException;
42  import org.codehaus.plexus.archiver.UnArchiver;
43  import org.codehaus.plexus.archiver.manager.ArchiverManager;
44  import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
45  import org.codehaus.plexus.components.interactivity.InputHandler;
46  import org.codehaus.plexus.util.FileUtils;
47  import org.codehaus.plexus.util.StringUtils;
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     public InstallPluginsMojo()
135     {
136         // used for plexus init.
137     }
138 
139     // used primarily for testing.
140     protected InstallPluginsMojo( File eclipseDir, boolean overwrite, List dependencyArtifacts,
141                                   String pluginDependencyTypes, ArtifactRepository localRepository,
142                                   MavenProjectBuilder projectBuilder, ArchiverManager archiverManager,
143                                   InputHandler inputHandler, Log log )
144     {
145         this.eclipseDir = eclipseDir;
146         this.overwrite = overwrite;
147         this.artifacts = dependencyArtifacts;
148         this.pluginDependencyTypes = pluginDependencyTypes;
149         this.localRepository = localRepository;
150         this.projectBuilder = projectBuilder;
151         this.archiverManager = archiverManager;
152         this.inputHandler = inputHandler;
153         setLog( log );
154     }
155 
156     /**
157      * Traverse the list of resolved dependency artifacts. For each one having a type that is listed in the
158      * pluginDependencyTypes parameter value, resolve the associated project metadata (POM), and perform install(..) on
159      * that artifact.
160      */
161     public void execute()
162         throws MojoExecutionException, MojoFailureException
163     {
164         if ( eclipseDir == null )
165         {
166             getLog().info( "Eclipse directory? " );
167 
168             String eclipseDirString;
169             try
170             {
171                 eclipseDirString = inputHandler.readLine();
172             }
173             catch ( IOException e )
174             {
175                 throw new MojoExecutionException( "Unable to read from standard input", e );
176             }
177 
178             eclipseDir = new File( eclipseDirString );
179         }
180 
181         if ( eclipseDir.exists() && !eclipseDir.isDirectory() )
182         {
183             throw new MojoFailureException( "Invalid Eclipse directory: " + eclipseDir );
184         }
185         else if ( !eclipseDir.exists() )
186         {
187             eclipseDir.mkdirs();
188         }
189 
190         for ( Iterator it = artifacts.iterator(); it.hasNext(); )
191         {
192             Artifact artifact = (Artifact) it.next();
193 
194             if ( pluginDependencyTypes.indexOf( artifact.getType() ) > -1 )
195             {
196                 getLog().debug( "Processing Eclipse plugin dependency: " + artifact.getId() );
197 
198                 MavenProject project;
199 
200                 try
201                 {
202                     project =
203                         projectBuilder.buildFromRepository( artifact, Collections.EMPTY_LIST, localRepository, true );
204                 }
205                 catch ( ProjectBuildingException e )
206                 {
207                     throw new MojoExecutionException( "Failed to load project metadata (POM) for: " + artifact.getId(),
208                                                       e );
209                 }
210 
211                 install( artifact, project );
212             }
213             else
214             {
215                 getLog().debug(
216                                 "Skipping dependency: " + artifact.getId() +
217                                     ". Set pluginDependencyTypes with a comma-separated list of types to change this." );
218             }
219         }
220     }
221 
222     /**
223      * <p>
224      * Install the plugin into the eclipse instance's /plugins directory
225      * </p>
226      * <ol>
227      * <li>Determine whether the plugin should be extracted into a directory or not</li>
228      * <li>If the plugin's target location exists, or overwrite is set to true:
229      * <ol type="a">
230      * <li>if extract, ensure the plugin target location exists (mkdirs), and extract there.</li>
231      * <li>copy the plugin file from the local repository to the target location</li>
232      * </ol>
233      * <p>
234      * Warn whenever a plugin will overwrite an existing file or directory, and emit an INFO message whenever a plugin
235      * installation is skipped because of an existing file and overwrite == false.
236      * </p>
237      * 
238      * @param artifact The plugin dependency as it has been resolved.
239      * @param project The project metadata for the accompanying plugin-dependency artifact, used to determine whether to
240      *            install as a jar or as a directory
241      * @throws MojoExecutionException In the event the plugin should be extracted but cannot, or the file copy fails (in
242      *             the event it should not be extracted)
243      * @throws MojoFailureException In the event that the plugins target directory (inside the Eclipse instance
244      *             directory) does not exist, or is not a directory.
245      */
246     private void install( Artifact artifact, MavenProject project )
247         throws MojoExecutionException, MojoFailureException
248     {
249         if ( pluginsDir == null )
250         {
251             pluginsDir = new File( eclipseDir, "plugins" );
252         }
253 
254         if ( !pluginsDir.exists() || !pluginsDir.isDirectory() )
255         {
256             throw new MojoFailureException( "Invalid Eclipse directory: " + eclipseDir +
257                 " (plugins directory is missing or not a directory)." );
258         }
259 
260         boolean installAsJar = true;
261 
262         Properties properties = project.getProperties();
263         if ( properties != null )
264         {
265             installAsJar = !Boolean.valueOf( properties.getProperty( PROP_UNPACK_PLUGIN, "false" ) ).booleanValue();
266         }
267 
268         Attributes attributes = null;
269         try
270         {
271             // don't verify, plugins zipped by eclipse:make-artifacts could have a bad signature
272             JarFile jar = new JarFile( artifact.getFile(), false );
273             Manifest manifest = jar.getManifest();
274             attributes = manifest.getMainAttributes();
275         }
276         catch ( IOException e )
277         {
278             throw new MojoExecutionException( "Unable to read manifest of plugin " +
279                 artifact.getFile().getAbsolutePath(), e );
280         }
281 
282         String bundleVersion = attributes.getValue( "Bundle-Version" );
283         String pluginName = formatEclipsePluginName( artifact, bundleVersion );
284 
285         File pluginFile = new File( pluginsDir, pluginName + ".jar" );
286         File pluginDir = new File( pluginsDir, pluginName );
287 
288         boolean skipped = true;
289 
290         /* check if artifact is an OSGi bundle and ignore if not */
291         Object bundleName = attributes.getValue( "Bundle-Name" );
292         if ( bundleName == null )
293         {
294             getLog().debug(
295                             "Ignoring " + artifact.getArtifactId() +
296                                 " as it is not an OSGi bundle (no Bundle-Name in manifest)" );
297             return;
298         }
299 
300         if ( overwrite )
301         {
302             if ( pluginFile.exists() || pluginDir.exists() )
303             {
304                 getLog().warn( "Overwriting old plugin with contents of: " + artifact.getId() );
305 
306                 getLog().debug( "Removing old plugin from both: " + pluginFile + " and: " + pluginDir );
307 
308                 try
309                 {
310                     FileUtils.forceDelete( pluginDir );
311                     FileUtils.forceDelete( pluginFile );
312                 }
313                 catch ( IOException e )
314                 {
315                     throw new MojoExecutionException( "Failed to remove old plugin from: " + pluginFile + " or: " +
316                         pluginDir, e );
317                 }
318 
319                 getLog().debug( "Removal of old plugin is complete; proceeding with plugin installation." );
320             }
321 
322             performFileOperations( installAsJar, artifact, pluginFile, pluginDir );
323 
324             skipped = false;
325         }
326         else if ( installAsJar && !pluginFile.exists() )
327         {
328             performFileOperations( installAsJar, artifact, pluginFile, pluginDir );
329 
330             skipped = false;
331         }
332         else if ( !installAsJar && !pluginDir.exists() )
333         {
334             performFileOperations( installAsJar, artifact, pluginFile, pluginDir );
335 
336             skipped = false;
337         }
338 
339         if ( skipped )
340         {
341             if ( installAsJar )
342             {
343                 getLog().info(
344                                "Skipping plugin installation for: " + artifact.getId() + "; file: " + pluginFile +
345                                    " already exists. Set overwrite = true to override this." );
346             }
347             else if ( !installAsJar )
348             {
349                 getLog().info(
350                                "Skipping plugin installation for: " + artifact.getId() + "; directory: " + pluginDir +
351                                    " already exists. Set overwrite = true to override this." );
352             }
353         }
354     }
355 
356     private void performFileOperations( boolean installAsJar, Artifact artifact, File pluginFile, File pluginDir )
357         throws MojoExecutionException
358     {
359         File artifactFile = artifact.getFile();
360 
361         if ( installAsJar )
362         {
363             try
364             {
365                 getLog().debug( "Copying: " + artifact.getId() + " to: " + pluginFile );
366 
367                 FileUtils.copyFile( artifactFile, pluginFile );
368             }
369             catch ( IOException e )
370             {
371                 throw new MojoExecutionException( "Failed to copy Eclipse plugin: " + artifact.getId() + "\nfrom: " +
372                     artifact.getFile() + "\nto: " + pluginFile, e );
373             }
374         }
375         else
376         {
377             try
378             {
379                 getLog().debug( "Expanding: " + artifact.getId() + " into: " + pluginDir );
380 
381                 pluginDir.mkdirs();
382 
383                 UnArchiver unarchiver = archiverManager.getUnArchiver( artifactFile );
384 
385                 unarchiver.setSourceFile( artifactFile );
386                 unarchiver.setDestDirectory( pluginDir );
387                 unarchiver.extract();
388             }
389             catch ( NoSuchArchiverException e )
390             {
391                 throw new MojoExecutionException( "Could not find unarchiver for: " + artifactFile, e );
392             }
393             catch ( ArchiverException e )
394             {
395                 throw new MojoExecutionException( "Could not extract: " + artifactFile, e );
396             }
397             catch ( IOException e )
398             {
399                 throw new MojoExecutionException( "Could not extract: " + artifactFile, e );
400             }
401         }
402     }
403 
404     /**
405      * <p>
406      * Format the artifact information into an Eclipse-friendly plugin name. Currently, this is just:
407      * <code>artifactId + "_" + bundle version</code> if bundle version is not null.
408      * </p>
409      */
410     private String formatEclipsePluginName( Artifact artifact, String bundleVersion )
411     {
412         if ( bundleVersion == null )
413         {
414             return artifact.getArtifactId() + StringUtils.replace( artifact.getVersion(), "-", "." );
415         }
416         else
417         {
418             return artifact.getArtifactId() + "_" + bundleVersion;
419         }
420     }
421 
422 }