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