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: "
222                                     + artifact.getId()
223                                     + ". Set pluginDependencyTypes with a comma-separated list of types to change this." );
224             }
225         }
226     }
227 
228     /**
229      * <p>
230      * Install the plugin into the eclipse instance's /plugins directory
231      * </p>
232      * <ol>
233      * <li>Determine whether the plugin should be extracted into a directory or not</li>
234      * <li>If the plugin's target location exists, or overwrite is set to true:
235      * <ol type="a">
236      * <li>if extract, ensure the plugin target location exists (mkdirs), and extract there.</li>
237      * <li>copy the plugin file from the local repository to the target location</li>
238      * </ol>
239      * <p>
240      * Warn whenever a plugin will overwrite an existing file or directory, and emit an INFO message whenever a plugin
241      * installation is skipped because of an existing file and overwrite == false.
242      * </p>
243      * 
244      * @param artifact The plugin dependency as it has been resolved.
245      * @param project The project metadata for the accompanying plugin-dependency artifact, used to determine whether to
246      *            install as a jar or as a directory
247      * @throws MojoExecutionException In the event the plugin should be extracted but cannot, or the file copy fails (in
248      *             the event it should not be extracted)
249      * @throws MojoFailureException In the event that the plugins target directory (inside the Eclipse instance
250      *             directory) does not exist, or is not a directory.
251      */
252     private void install( Artifact artifact, MavenProject project )
253         throws MojoExecutionException, MojoFailureException
254     {
255         if ( pluginsDir == null )
256         {
257             pluginsDir = new File( eclipseDir, "plugins" );
258         }
259 
260         if ( !pluginsDir.exists() || !pluginsDir.isDirectory() )
261         {
262             throw new MojoFailureException( "Invalid Eclipse directory: " + eclipseDir
263                 + " (plugins directory is missing or not a directory)." );
264         }
265 
266         boolean installAsJar = true;
267 
268         Properties properties = project.getProperties();
269         if ( properties != null )
270         {
271             installAsJar = !Boolean.valueOf( properties.getProperty( PROP_UNPACK_PLUGIN, "false" ) ).booleanValue();
272         }
273 
274         Attributes attributes = null;
275         try
276         {
277             // don't verify, plugins zipped by eclipse:make-artifacts could have a bad signature
278             JarFile jar = new JarFile( artifact.getFile(), false );
279             Manifest manifest = jar.getManifest();
280             if ( manifest == null )
281             {
282                 getLog().debug(
283                                 "Ignoring " + artifact.getArtifactId()
284                                     + " as it is does not have a Manifest (and so is not an OSGi bundle)" );
285                 return;
286             }
287             attributes = manifest.getMainAttributes();
288         }
289         catch ( IOException e )
290         {
291             throw new MojoExecutionException( "Unable to read manifest of plugin "
292                 + artifact.getFile().getAbsolutePath(), e );
293         }
294 
295         String pluginName = formatEclipsePluginName( artifact );
296 
297         File pluginFile = new File( pluginsDir, pluginName + ".jar" );
298         File pluginDir = new File( pluginsDir, pluginName );
299 
300         boolean skipped = true;
301 
302         /* check if artifact is an OSGi bundle and ignore if not */
303         Object bundleName = attributes.getValue( "Bundle-Name" );
304         Object bundleSymbolicName = attributes.getValue( "Bundle-SymbolicName" );
305         if ( bundleSymbolicName == null && bundleName == null )
306         {
307             getLog().debug(
308                             "Ignoring " + artifact.getArtifactId()
309                                 + " as it is not an OSGi bundle (no Bundle-SymbolicName or Bundle-Name in manifest)" );
310             return;
311         }
312 
313         if ( overwrite )
314         {
315             if ( pluginFile.exists() || pluginDir.exists() )
316             {
317                 getLog().warn( "Overwriting old plugin with contents of: " + artifact.getId() );
318 
319                 getLog().debug( "Removing old plugin from both: " + pluginFile + " and: " + pluginDir );
320 
321                 try
322                 {
323                     FileUtils.forceDelete( pluginDir );
324                     FileUtils.forceDelete( pluginFile );
325                 }
326                 catch ( IOException e )
327                 {
328                     throw new MojoExecutionException( "Failed to remove old plugin from: " + pluginFile + " or: "
329                         + pluginDir, e );
330                 }
331 
332                 getLog().debug( "Removal of old plugin is complete; proceeding with plugin installation." );
333             }
334 
335             performFileOperations( installAsJar, artifact, pluginFile, pluginDir );
336 
337             skipped = false;
338         }
339         else if ( installAsJar && !pluginFile.exists() )
340         {
341             performFileOperations( installAsJar, artifact, pluginFile, pluginDir );
342 
343             skipped = false;
344         }
345         else if ( !installAsJar && !pluginDir.exists() )
346         {
347             performFileOperations( installAsJar, artifact, pluginFile, pluginDir );
348 
349             skipped = false;
350         }
351 
352         if ( skipped )
353         {
354             if ( installAsJar )
355             {
356                 getLog().info(
357                                "Skipping plugin installation for: " + artifact.getId() + "; file: " + pluginFile
358                                    + " already exists. Set overwrite = true to override this." );
359             }
360             else if ( !installAsJar )
361             {
362                 getLog().info(
363                                "Skipping plugin installation for: " + artifact.getId() + "; directory: " + pluginDir
364                                    + " already exists. Set overwrite = true to override this." );
365             }
366         }
367     }
368 
369     private void performFileOperations( boolean installAsJar, Artifact artifact, File pluginFile, File pluginDir )
370         throws MojoExecutionException
371     {
372         File artifactFile = artifact.getFile();
373 
374         if ( installAsJar )
375         {
376             try
377             {
378                 getLog().debug( "Copying: " + artifact.getId() + " to: " + pluginFile );
379 
380                 FileUtils.copyFile( artifactFile, pluginFile );
381             }
382             catch ( IOException e )
383             {
384                 throw new MojoExecutionException( "Failed to copy Eclipse plugin: " + artifact.getId() + "\nfrom: "
385                     + artifact.getFile() + "\nto: " + pluginFile, e );
386             }
387         }
388         else
389         {
390             try
391             {
392                 getLog().debug( "Expanding: " + artifact.getId() + " into: " + pluginDir );
393 
394                 pluginDir.mkdirs();
395 
396                 UnArchiver unarchiver = archiverManager.getUnArchiver( artifactFile );
397 
398                 unarchiver.setSourceFile( artifactFile );
399                 unarchiver.setDestDirectory( pluginDir );
400                 unarchiver.extract();
401             }
402             catch ( NoSuchArchiverException e )
403             {
404                 throw new MojoExecutionException( "Could not find unarchiver for: " + artifactFile, e );
405             }
406             catch ( ArchiverException e )
407             {
408                 throw new MojoExecutionException( "Could not extract: " + artifactFile, e );
409             }
410             catch ( IOException e )
411             {
412                 throw new MojoExecutionException( "Could not extract: " + artifactFile, e );
413             }
414         }
415     }
416 
417     /**
418      * <p>
419      * Format the artifact information into an Eclipse-friendly plug-in name. Delegates to maven2OsgiConverter to obtain
420      * bundle symbolic name and version.
421      * </p>
422      */
423     private String formatEclipsePluginName( Artifact artifact )
424     {
425         return maven2OsgiConverter.getBundleSymbolicName( artifact ) + "_"
426             + maven2OsgiConverter.getVersion( artifact.getVersion() );
427     }
428 
429 }