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.ide;
20  
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.Iterator;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Properties;
34  import java.util.Set;
35  import java.util.TreeSet;
36  import java.util.jar.Attributes;
37  import java.util.jar.JarFile;
38  import java.util.jar.Manifest;
39  import java.util.zip.ZipFile;
40  
41  import org.apache.maven.artifact.Artifact;
42  import org.apache.maven.artifact.factory.ArtifactFactory;
43  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
44  import org.apache.maven.artifact.repository.ArtifactRepository;
45  import org.apache.maven.artifact.resolver.ArtifactCollector;
46  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
47  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
48  import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
49  import org.apache.maven.artifact.resolver.ArtifactResolver;
50  import org.apache.maven.artifact.resolver.DebugResolutionListener;
51  import org.apache.maven.artifact.resolver.ResolutionNode;
52  import org.apache.maven.artifact.resolver.WarningResolutionListener;
53  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
54  import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
55  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
56  import org.apache.maven.artifact.versioning.VersionRange;
57  import org.apache.maven.model.Dependency;
58  import org.apache.maven.model.DependencyManagement;
59  import org.apache.maven.model.Exclusion;
60  import org.apache.maven.plugin.AbstractMojo;
61  import org.apache.maven.plugin.MojoExecutionException;
62  import org.apache.maven.plugin.MojoFailureException;
63  import org.apache.maven.plugin.eclipse.Constants;
64  import org.apache.maven.project.MavenProject;
65  import org.codehaus.plexus.logging.LogEnabled;
66  import org.codehaus.plexus.logging.Logger;
67  import org.codehaus.plexus.util.IOUtil;
68  
69  /**
70   * Abstract base plugin which takes care of the common stuff usually needed by maven IDE plugins. A plugin extending
71   * AbstractIdeSupportMojo should implement the <code>setup()</code> and <code>writeConfiguration()</code> methods,
72   * plus the getters needed to get the various configuration flags and required components. The lifecycle:
73   * 
74   * <pre>
75   *       *** calls setup() where you can configure your specific stuff and stop the mojo from execute if appropriate ***
76   *       - manually resolve project dependencies, NOT failing if a dependency is missing
77   *       - compute project references (reactor projects) if the getUseProjectReferences() flag is set
78   *       - download sources/javadocs if the getDownloadSources() flag is set
79   *       *** calls writeConfiguration(), passing the list of resolved referenced dependencies ***
80   *       - report the list of missing sources or just tell how to turn this feature on if the flag was disabled
81   * </pre>
82   * 
83   * @author Fabrizio Giustina
84   * @version $Id: AbstractIdeSupportMojo.java 597060 2007-11-21 12:49:31Z aheritier $
85   */
86  public abstract class AbstractIdeSupportMojo
87      extends AbstractMojo
88      implements LogEnabled
89  {
90  
91      /**
92       * The project whose project files to create.
93       * 
94       * @parameter expression="${project}"
95       * @required
96       * @readonly
97       */
98      protected MavenProject project;
99  
100     /**
101      * The currently executed project (can be a reactor project).
102      * 
103      * @parameter expression="${executedProject}"
104      * @readonly
105      */
106     protected MavenProject executedProject;
107 
108     /**
109      * The project packaging.
110      * 
111      * @parameter expression="${project.packaging}"
112      */
113     protected String packaging;
114 
115     /**
116      * Artifact factory, needed to download source jars for inclusion in classpath.
117      * 
118      * @component role="org.apache.maven.artifact.factory.ArtifactFactory"
119      * @required
120      * @readonly
121      */
122     protected ArtifactFactory artifactFactory;
123 
124     /**
125      * Artifact resolver, needed to download source jars for inclusion in classpath.
126      * 
127      * @component role="org.apache.maven.artifact.resolver.ArtifactResolver"
128      * @required
129      * @readonly
130      */
131     protected ArtifactResolver artifactResolver;
132 
133     /**
134      * Artifact collector, needed to resolve dependencies.
135      * 
136      * @component role="org.apache.maven.artifact.resolver.ArtifactCollector"
137      * @required
138      * @readonly
139      */
140     protected ArtifactCollector artifactCollector;
141 
142     /**
143      * @component role="org.apache.maven.artifact.metadata.ArtifactMetadataSource" hint="maven"
144      */
145     protected ArtifactMetadataSource artifactMetadataSource;
146 
147     /**
148      * Remote repositories which will be searched for source attachments.
149      * 
150      * @parameter expression="${project.remoteArtifactRepositories}"
151      * @required
152      * @readonly
153      */
154     protected List remoteArtifactRepositories;
155 
156     /**
157      * Local maven repository.
158      * 
159      * @parameter expression="${localRepository}"
160      * @required
161      * @readonly
162      */
163     protected ArtifactRepository localRepository;
164 
165     /**
166      * If the executed project is a reactor project, this will contains the full list of projects in the reactor.
167      * 
168      * @parameter expression="${reactorProjects}"
169      * @required
170      * @readonly
171      */
172     protected List reactorProjects;
173 
174     /**
175      * Skip the operation when true.
176      * 
177      * @parameter expression="${eclipse.skip}" default-value="false"
178      */
179     private boolean skip;
180 
181     /**
182      * Enables/disables the downloading of source attachments. Defaults to false. When this flag is <code>true</code>
183      * remote repositories are checked for sources: in order to avoid repeated check for unavailable source archives, a
184      * status cache is mantained into the target dir of the root project. Run <code>mvn:clean</code> or delete the
185      * file <code>mvn-eclipse-cache.properties</code> in order to reset this cache.
186      * 
187      * @parameter expression="${downloadSources}"
188      */
189     protected boolean downloadSources;
190 
191     /**
192      * Enables/disables the downloading of javadoc attachments. Defaults to false. When this flag is <code>true</code>
193      * remote repositories are checked for javadocs: in order to avoid repeated check for unavailable javadoc archives,
194      * a status cache is mantained into the target dir of the root project. Run <code>mvn:clean</code> or delete the
195      * file <code>mvn-eclipse-cache.properties</code> in order to reset this cache.
196      * 
197      * @parameter expression="${downloadJavadocs}"
198      */
199     protected boolean downloadJavadocs;
200 
201     /**
202      * Plexus logger needed for debugging manual artifact resolution.
203      */
204     private Logger logger;
205 
206     /**
207      * Getter for <code>artifactMetadataSource</code>.
208      * 
209      * @return Returns the artifactMetadataSource.
210      */
211     public ArtifactMetadataSource getArtifactMetadataSource()
212     {
213         return this.artifactMetadataSource;
214     }
215 
216     /**
217      * Setter for <code>artifactMetadataSource</code>.
218      * 
219      * @param artifactMetadataSource The artifactMetadataSource to set.
220      */
221     public void setArtifactMetadataSource( ArtifactMetadataSource artifactMetadataSource )
222     {
223         this.artifactMetadataSource = artifactMetadataSource;
224     }
225 
226     /**
227      * Getter for <code>project</code>.
228      * 
229      * @return Returns the project.
230      */
231     public MavenProject getProject()
232     {
233         return this.project;
234     }
235 
236     /**
237      * Setter for <code>project</code>.
238      * 
239      * @param project The project to set.
240      */
241     public void setProject( MavenProject project )
242     {
243         this.project = project;
244     }
245 
246     /**
247      * Getter for <code>reactorProjects</code>.
248      * 
249      * @return Returns the reactorProjects.
250      */
251     public List getReactorProjects()
252     {
253         return this.reactorProjects;
254     }
255 
256     /**
257      * Setter for <code>reactorProjects</code>.
258      * 
259      * @param reactorProjects The reactorProjects to set.
260      */
261     public void setReactorProjects( List reactorProjects )
262     {
263         this.reactorProjects = reactorProjects;
264     }
265 
266     /**
267      * Getter for <code>remoteArtifactRepositories</code>.
268      * 
269      * @return Returns the remoteArtifactRepositories.
270      */
271     public List getRemoteArtifactRepositories()
272     {
273         return this.remoteArtifactRepositories;
274     }
275 
276     /**
277      * Setter for <code>remoteArtifactRepositories</code>.
278      * 
279      * @param remoteArtifactRepositories The remoteArtifactRepositories to set.
280      */
281     public void setRemoteArtifactRepositories( List remoteArtifactRepositories )
282     {
283         this.remoteArtifactRepositories = remoteArtifactRepositories;
284     }
285 
286     /**
287      * Getter for <code>artifactFactory</code>.
288      * 
289      * @return Returns the artifactFactory.
290      */
291     public ArtifactFactory getArtifactFactory()
292     {
293         return this.artifactFactory;
294     }
295 
296     /**
297      * Setter for <code>artifactFactory</code>.
298      * 
299      * @param artifactFactory The artifactFactory to set.
300      */
301     public void setArtifactFactory( ArtifactFactory artifactFactory )
302     {
303         this.artifactFactory = artifactFactory;
304     }
305 
306     /**
307      * Getter for <code>artifactResolver</code>.
308      * 
309      * @return Returns the artifactResolver.
310      */
311     public ArtifactResolver getArtifactResolver()
312     {
313         return this.artifactResolver;
314     }
315 
316     /**
317      * Setter for <code>artifactResolver</code>.
318      * 
319      * @param artifactResolver The artifactResolver to set.
320      */
321     public void setArtifactResolver( ArtifactResolver artifactResolver )
322     {
323         this.artifactResolver = artifactResolver;
324     }
325 
326     /**
327      * Getter for <code>executedProject</code>.
328      * 
329      * @return Returns the executedProject.
330      */
331     public MavenProject getExecutedProject()
332     {
333         return this.executedProject;
334     }
335 
336     /**
337      * Setter for <code>executedProject</code>.
338      * 
339      * @param executedProject The executedProject to set.
340      */
341     public void setExecutedProject( MavenProject executedProject )
342     {
343         this.executedProject = executedProject;
344     }
345 
346     /**
347      * Getter for <code>localRepository</code>.
348      * 
349      * @return Returns the localRepository.
350      */
351     public ArtifactRepository getLocalRepository()
352     {
353         return this.localRepository;
354     }
355 
356     /**
357      * Setter for <code>localRepository</code>.
358      * 
359      * @param localRepository The localRepository to set.
360      */
361     public void setLocalRepository( ArtifactRepository localRepository )
362     {
363         this.localRepository = localRepository;
364     }
365 
366     /**
367      * Getter for <code>downloadJavadocs</code>.
368      * 
369      * @return Returns the downloadJavadocs.
370      */
371     public boolean getDownloadJavadocs()
372     {
373         return this.downloadJavadocs;
374     }
375 
376     /**
377      * Setter for <code>downloadJavadocs</code>.
378      * 
379      * @param downloadJavadocs The downloadJavadocs to set.
380      */
381     public void setDownloadJavadocs( boolean downloadJavadoc )
382     {
383         this.downloadJavadocs = downloadJavadoc;
384     }
385 
386     /**
387      * Getter for <code>downloadSources</code>.
388      * 
389      * @return Returns the downloadSources.
390      */
391     public boolean getDownloadSources()
392     {
393         return this.downloadSources;
394     }
395 
396     /**
397      * Setter for <code>downloadSources</code>.
398      * 
399      * @param downloadSources The downloadSources to set.
400      */
401     public void setDownloadSources( boolean downloadSources )
402     {
403         this.downloadSources = downloadSources;
404     }
405 
406     protected void setResolveDependencies( boolean resolveDependencies )
407     {
408         this.resolveDependencies = resolveDependencies;
409     }
410 
411     protected boolean isResolveDependencies()
412     {
413         return resolveDependencies;
414     }
415 
416     /**
417      * return <code>false</code> if projects available in a reactor build should be considered normal dependencies,
418      * <code>true</code> if referenced project will be linked and not need artifact resolution.
419      * 
420      * @return <code>true</code> if referenced project will be linked and not need artifact resolution
421      */
422     protected abstract boolean getUseProjectReferences();
423 
424     /**
425      * Hook for preparation steps before the actual plugin execution.
426      * 
427      * @return <code>true</code> if execution should continue or <code>false</code> if not.
428      * @throws MojoExecutionException generic mojo exception
429      */
430     protected abstract boolean setup()
431         throws MojoExecutionException;
432 
433     /**
434      * Main plugin method where dependencies should be processed in order to generate IDE configuration files.
435      * 
436      * @param deps list of <code>IdeDependency</code> objects, with artifacts, sources and javadocs already resolved
437      * @throws MojoExecutionException generic mojo exception
438      */
439     protected abstract void writeConfiguration( IdeDependency[] deps )
440         throws MojoExecutionException;
441 
442     /**
443      * Not a plugin parameter. Collect the list of dependencies with a missing source artifact for the final report.
444      */
445     private List missingSourceDependencies = new ArrayList();
446 
447     /**
448      * Not a plugin parameter. Collect the list of dependencies with a missing javadoc artifact for the final report.
449      */
450     // TODO merge this with the missingSourceDependencies in a classifier based map?
451     private List missingJavadocDependencies = new ArrayList();
452 
453     /**
454      * Cached array of resolved dependencies.
455      */
456     private IdeDependency[] ideDeps;
457 
458     /**
459      * Flag for mojo implementations to control whether normal maven dependencies should be resolved. Default value is
460      * true.
461      */
462     private boolean resolveDependencies = true;
463 
464     /**
465      * @see org.codehaus.plexus.logging.LogEnabled#enableLogging(org.codehaus.plexus.logging.Logger)
466      */
467     public void enableLogging( Logger logger )
468     {
469         this.logger = logger;
470     }
471 
472     /**
473      * @see org.apache.maven.plugin.Mojo#execute()
474      */
475     public final void execute()
476         throws MojoExecutionException, MojoFailureException
477     {
478         if ( skip )
479         {
480             return;
481         }
482 
483         boolean processProject = setup();
484         if ( !processProject )
485         {
486             return;
487         }
488 
489         // resolve artifacts
490         IdeDependency[] deps = doDependencyResolution();
491 
492         resolveSourceAndJavadocArtifacts( deps );
493 
494         writeConfiguration( deps );
495 
496         reportMissingArtifacts();
497 
498     }
499 
500     /**
501      * Resolve project dependencies. Manual resolution is needed in order to avoid resolution of multiproject artifacts
502      * (if projects will be linked each other an installed jar is not needed) and to avoid a failure when a jar is
503      * missing.
504      * 
505      * @throws MojoExecutionException if dependencies can't be resolved
506      * @return resolved IDE dependencies, with attached jars for non-reactor dependencies
507      */
508     protected IdeDependency[] doDependencyResolution()
509         throws MojoExecutionException
510     {
511         if ( ideDeps == null )
512         {
513             if ( resolveDependencies )
514             {
515                 MavenProject project = getProject();
516                 ArtifactRepository localRepo = getLocalRepository();
517 
518                 List deps = getProject().getDependencies();
519 
520                 // Collect the list of resolved IdeDependencies.
521                 List dependencies = new ArrayList();
522 
523                 if ( deps != null )
524                 {
525                     Map managedVersions =
526                         createManagedVersionMap( getArtifactFactory(), project.getId(),
527                                                  project.getDependencyManagement() );
528 
529                     ArtifactResolutionResult artifactResolutionResult = null;
530 
531                     try
532                     {
533 
534                         List listeners = new ArrayList();
535 
536                         if ( logger.isDebugEnabled() )
537                         {
538                             listeners.add( new DebugResolutionListener( logger ) );
539                         }
540 
541                         listeners.add( new WarningResolutionListener( logger ) );
542 
543                         artifactResolutionResult =
544                             artifactCollector.collect( getProjectArtifacts(), project.getArtifact(), managedVersions,
545                                                        localRepo, project.getRemoteArtifactRepositories(),
546                                                        getArtifactMetadataSource(), null, listeners );
547                     }
548                     catch ( ArtifactResolutionException e )
549                     {
550                         getLog().debug( e.getMessage(), e );
551                         getLog().error(
552                                         Messages.getString( "artifactresolution", new Object[] { //$NON-NLS-1$
553                                                             e.getGroupId(), e.getArtifactId(), e.getVersion(),
554                                                                 e.getMessage() } ) );
555 
556                         // if we are here artifactResolutionResult is null, create a project without dependencies but
557                         // don't fail
558                         // (this could be a reactor projects, we don't want to fail everything)
559                         // Causes MECLIPSE-185. Not sure if it should be handled this way??
560                         return new IdeDependency[0];
561                     }
562 
563                     // keep track of added reactor projects in order to avoid duplicates
564                     Set emittedReactorProjectId = new HashSet();
565 
566                     for ( Iterator i = artifactResolutionResult.getArtifactResolutionNodes().iterator(); i.hasNext(); )
567                     {
568 
569                         ResolutionNode node = (ResolutionNode) i.next();
570                         int dependencyDepth = node.getDepth();
571                         Artifact art = node.getArtifact();
572                         boolean isReactorProject = getUseProjectReferences() && isAvailableAsAReactorProject( art );
573 
574                         // don't resolve jars for reactor projects
575                         if ( !isReactorProject )
576                         {
577                             try
578                             {
579                                 artifactResolver.resolve( art, node.getRemoteRepositories(), localRepository );
580                             }
581                             catch ( ArtifactNotFoundException e )
582                             {
583                                 getLog().debug( e.getMessage(), e );
584                                 getLog().warn(
585                                                Messages.getString( "artifactdownload", new Object[] { //$NON-NLS-1$
586                                                                    e.getGroupId(), e.getArtifactId(), e.getVersion(),
587                                                                        e.getMessage() } ) );
588                             }
589                             catch ( ArtifactResolutionException e )
590                             {
591                                 getLog().debug( e.getMessage(), e );
592                                 getLog().warn(
593                                                Messages.getString( "artifactresolution", new Object[] { //$NON-NLS-1$
594                                                                    e.getGroupId(), e.getArtifactId(), e.getVersion(),
595                                                                        e.getMessage() } ) );
596                             }
597                         }
598 
599                         if ( !isReactorProject ||
600                             emittedReactorProjectId.add( art.getGroupId() + '-' + art.getArtifactId() ) )
601                         {
602 
603                             // the following doesn't work: art.getArtifactHandler().getPackaging() always returns "jar"
604                             // also
605                             // if the packaging specified in pom.xml is different.
606 
607                             // osgi-bundle packaging is provided by the felix osgi plugin
608                             // eclipse-plugin packaging is provided by this eclipse plugin
609                             // String packaging = art.getArtifactHandler().getPackaging();
610                             // boolean isOsgiBundle = "osgi-bundle".equals( packaging ) || "eclipse-plugin".equals(
611                             // packaging );
612 
613                             // we need to check the manifest, if "Bundle-SymbolicName" is there the artifact can be
614                             // considered
615                             // an osgi bundle
616                             boolean isOsgiBundle = false;
617                             String osgiSymbolicName = null;
618                             if ( art.getFile() != null )
619                             {
620                                 JarFile jarFile = null;
621                                 try
622                                 {
623                                     jarFile = new JarFile( art.getFile(), false, ZipFile.OPEN_READ );
624 
625                                     Manifest manifest = jarFile.getManifest();
626                                     if ( manifest != null )
627                                     {
628                                         osgiSymbolicName =
629                                             manifest.getMainAttributes().getValue(
630                                                                                    new Attributes.Name(
631                                                                                                         "Bundle-SymbolicName" ) );
632                                     }
633                                 }
634                                 catch ( IOException e )
635                                 {
636                                     getLog().info( "Unable to read jar manifest from " + art.getFile() );
637                                 }
638                                 finally
639                                 {
640                                     if ( jarFile != null )
641                                     {
642                                         try
643                                         {
644                                             jarFile.close();
645                                         }
646                                         catch ( IOException e )
647                                         {
648                                             // ignore
649                                         }
650                                     }
651                                 }
652                             }
653 
654                             isOsgiBundle = osgiSymbolicName != null;
655 
656                             IdeDependency dep =
657                                 new IdeDependency( art.getGroupId(), art.getArtifactId(), art.getVersion(),
658                                                    art.getClassifier(), isReactorProject,
659                                                    Artifact.SCOPE_TEST.equals( art.getScope() ),
660                                                    Artifact.SCOPE_SYSTEM.equals( art.getScope() ),
661                                                    Artifact.SCOPE_PROVIDED.equals( art.getScope() ),
662                                                    art.getArtifactHandler().isAddedToClasspath(), art.getFile(),
663                                                    art.getType(), isOsgiBundle, osgiSymbolicName, dependencyDepth,
664                                                    getProjectNameForArifact( art ) );
665                             // no duplicate entries allowed. System paths can cause this problem.
666                             if ( !dependencies.contains( dep ) )
667                             {
668                                 dependencies.add( dep );
669                             }
670                         }
671 
672                     }
673 
674                     // @todo a final report with the list of
675                     // missingArtifacts?
676 
677                 }
678 
679                 ideDeps = (IdeDependency[]) dependencies.toArray( new IdeDependency[dependencies.size()] );
680             }
681             else
682             {
683                 ideDeps = new IdeDependency[0];
684             }
685         }
686 
687         return ideDeps;
688     }
689 
690     /**
691      * Find the name of the project as used in eclipse.
692      * 
693      * @param artifact The artifact to find the eclipse name for.
694      * @return The name os the eclipse project.
695      */
696     abstract public String getProjectNameForArifact( Artifact artifact );
697 
698     /**
699      * Returns the list of project artifacts. Also artifacts generated from referenced projects will be added, but with
700      * the <code>resolved</code> property set to true.
701      * 
702      * @return list of projects artifacts
703      * @throws MojoExecutionException if unable to parse dependency versions
704      */
705     private Set getProjectArtifacts()
706         throws MojoExecutionException
707     {
708         // keep it sorted, this should avoid random classpath order in tests
709         Set artifacts = new TreeSet();
710 
711         for ( Iterator dependencies = getProject().getDependencies().iterator(); dependencies.hasNext(); )
712         {
713             Dependency dependency = (Dependency) dependencies.next();
714 
715             String groupId = dependency.getGroupId();
716             String artifactId = dependency.getArtifactId();
717             VersionRange versionRange;
718             try
719             {
720                 versionRange = VersionRange.createFromVersionSpec( dependency.getVersion() );
721             }
722             catch ( InvalidVersionSpecificationException e )
723             {
724                 throw new MojoExecutionException(
725                                                   Messages.getString(
726                                                                       "unabletoparseversion", new Object[] { //$NON-NLS-1$
727                                                                       dependency.getArtifactId(),
728                                                                           dependency.getVersion(),
729                                                                           dependency.getManagementKey(), e.getMessage() } ),
730                                                   e );
731             }
732 
733             String type = dependency.getType();
734             if ( type == null )
735             {
736                 type = Constants.PROJECT_PACKAGING_JAR; //$NON-NLS-1$
737             }
738             String classifier = dependency.getClassifier();
739             boolean optional = dependency.isOptional();
740             String scope = dependency.getScope();
741             if ( scope == null )
742             {
743                 scope = Artifact.SCOPE_COMPILE;
744             }
745 
746             Artifact art =
747                 getArtifactFactory().createDependencyArtifact( groupId, artifactId, versionRange, type, classifier,
748                                                                scope, optional );
749 
750             if ( scope.equalsIgnoreCase( Artifact.SCOPE_SYSTEM ) )
751             {
752                 art.setFile( new File( dependency.getSystemPath() ) );
753             }
754 
755             List exclusions = new ArrayList();
756             for ( Iterator j = dependency.getExclusions().iterator(); j.hasNext(); )
757             {
758                 Exclusion e = (Exclusion) j.next();
759                 exclusions.add( e.getGroupId() + ":" + e.getArtifactId() ); //$NON-NLS-1$
760             }
761 
762             ArtifactFilter newFilter = new ExcludesArtifactFilter( exclusions );
763 
764             art.setDependencyFilter( newFilter );
765 
766             artifacts.add( art );
767         }
768 
769         return artifacts;
770     }
771 
772     /**
773      * Utility method that locates a project producing the given artifact.
774      * 
775      * @param artifact the artifact a project should produce.
776      * @return <code>true</code> if the artifact is produced by a reactor projectart.
777      */
778     private boolean isAvailableAsAReactorProject( Artifact artifact )
779     {
780         if ( reactorProjects != null )
781         {
782             for ( Iterator iter = reactorProjects.iterator(); iter.hasNext(); )
783             {
784                 MavenProject reactorProject = (MavenProject) iter.next();
785 
786                 if ( reactorProject.getGroupId().equals( artifact.getGroupId() ) &&
787                     reactorProject.getArtifactId().equals( artifact.getArtifactId() ) )
788                 {
789                     if ( reactorProject.getVersion().equals( artifact.getVersion() ) )
790                     {
791                         return true;
792                     }
793                     else
794                     {
795                         getLog().info(
796                                        "Artifact " +
797                                            artifact.getId() +
798                                            " already available as a reactor project, but with different version. Expected: " +
799                                            artifact.getVersion() + ", found: " + reactorProject.getVersion() );
800                     }
801                 }
802             }
803         }
804         return false;
805     }
806 
807     private Map createManagedVersionMap( ArtifactFactory artifactFactory, String projectId,
808                                          DependencyManagement dependencyManagement )
809         throws MojoExecutionException
810     {
811         Map map;
812         if ( dependencyManagement != null && dependencyManagement.getDependencies() != null )
813         {
814             map = new HashMap();
815             for ( Iterator i = dependencyManagement.getDependencies().iterator(); i.hasNext(); )
816             {
817                 Dependency d = (Dependency) i.next();
818 
819                 try
820                 {
821                     VersionRange versionRange = VersionRange.createFromVersionSpec( d.getVersion() );
822                     Artifact artifact =
823                         artifactFactory.createDependencyArtifact( d.getGroupId(), d.getArtifactId(), versionRange,
824                                                                   d.getType(), d.getClassifier(), d.getScope(),
825                                                                   d.isOptional() );
826                     map.put( d.getManagementKey(), artifact );
827                 }
828                 catch ( InvalidVersionSpecificationException e )
829                 {
830                     throw new MojoExecutionException( Messages.getString( "unabletoparseversion", new Object[] { //$NON-NLS-1$
831                                                                           projectId, d.getVersion(),
832                                                                               d.getManagementKey(), e.getMessage() } ),
833                                                       e );
834                 }
835             }
836         }
837         else
838         {
839             map = Collections.EMPTY_MAP;
840         }
841         return map;
842     }
843 
844     /**
845      * Find the reactor target dir. executedProject doesn't have the multiproject root dir set, and the only way to
846      * extract it is iterating on parent projects.
847      * 
848      * @param prj current project
849      * @return the parent target dir.
850      */
851     private File getReactorTargetDir( MavenProject prj )
852     {
853         if ( prj.getParent() != null )
854         {
855             if ( prj.getParent().getBasedir() != null && prj.getParent().getBasedir().exists() )
856             {
857                 return getReactorTargetDir( prj.getParent() );
858             }
859         }
860         return new File( prj.getBuild().getDirectory() );
861     }
862 
863     /**
864      * Resolve source artifacts and download them if <code>downloadSources</code> is <code>true</code>. Source and
865      * javadocs artifacts will be attached to the <code>IdeDependency</code> Resolve source and javadoc artifacts. The
866      * resolved artifacts will be downloaded based on the <code>downloadSources</code> and
867      * <code>downloadJavadocs</code> attributes. Source and
868      * 
869      * @param deps resolved dependencies
870      */
871     private void resolveSourceAndJavadocArtifacts( IdeDependency[] deps )
872     {
873 
874         File reactorTargetDir = getReactorTargetDir( project );
875         File unavailableArtifactsTmpFile = new File( reactorTargetDir, "mvn-eclipse-cache.properties" );
876 
877         getLog().info( "Using source status cache: " + unavailableArtifactsTmpFile.getAbsolutePath() );
878 
879         // create target dir if missing
880         if ( !unavailableArtifactsTmpFile.getParentFile().exists() )
881         {
882             unavailableArtifactsTmpFile.getParentFile().mkdirs();
883         }
884 
885         Properties unavailableArtifactsCache = new Properties();
886         if ( unavailableArtifactsTmpFile.exists() )
887         {
888             InputStream is = null;
889             try
890             {
891                 is = new FileInputStream( unavailableArtifactsTmpFile );
892                 unavailableArtifactsCache.load( is );
893             }
894             catch ( IOException e )
895             {
896                 getLog().warn( "Unable to read source status for reactor projects" );
897             }
898             finally
899             {
900                 IOUtil.close( is );
901             }
902 
903         }
904 
905         final List missingSources =
906             resolveDependenciesWithClassifier( deps, "sources", getDownloadSources(), unavailableArtifactsCache );
907         missingSourceDependencies.addAll( missingSources );
908 
909         final List missingJavadocs =
910             resolveDependenciesWithClassifier( deps, "javadoc", getDownloadJavadocs(), unavailableArtifactsCache );
911         missingJavadocDependencies.addAll( missingJavadocs );
912 
913         FileOutputStream fos = null;
914         try
915         {
916             fos = new FileOutputStream( unavailableArtifactsTmpFile );
917             unavailableArtifactsCache.store( fos, "Temporary index for unavailable sources and javadocs" );
918         }
919         catch ( IOException e )
920         {
921             getLog().warn( "Unable to cache source status for reactor projects" );
922         }
923         finally
924         {
925             IOUtil.close( fos );
926         }
927 
928     }
929 
930     /**
931      * Resolve the required artifacts for each of the dependency. <code>sources</code> or <code>javadoc</code>
932      * artifacts (depending on the <code>classifier</code>) are attached to the dependency.
933      * 
934      * @param deps resolved dependencies
935      * @param classifier the classifier we are looking for (either <code>sources</code> or <code>javadoc</code>)
936      * @param includeRemoteRepositories flag whether we should search remote repositories for the artifacts or not
937      * @param unavailableArtifactsCache cache of unavailable artifacts
938      * @return the list of dependencies for which the required artifact was not found
939      */
940     private List resolveDependenciesWithClassifier( IdeDependency[] deps, String inClassifier,
941                                                     boolean includeRemoteRepositories,
942                                                     Properties unavailableArtifactsCache )
943     {
944         List missingClassifierDependencies = new ArrayList();
945 
946         // if downloadSources is off, just check
947         // local repository for reporting missing source jars
948         List remoteRepos = includeRemoteRepositories ? getRemoteArtifactRepositories() : Collections.EMPTY_LIST;
949 
950         for ( int j = 0; j < deps.length; j++ )
951         {
952             IdeDependency dependency = deps[j];
953 
954             if ( dependency.isReferencedProject() || dependency.isSystemScoped() )
955             {
956                 // artifact not needed
957                 continue;
958             }
959 
960             // MECLIPSE-151 - if the dependency is a test, get the correct classifier for it. (ignore for javadocs)
961             String classifier = inClassifier;
962             if ( "sources".equals( classifier ) && "tests".equals( dependency.getClassifier() ) )
963             {
964                 classifier = "test-sources";
965             }
966 
967             if ( getLog().isDebugEnabled() )
968             {
969                 getLog().debug(
970                                 "Searching for sources for " + dependency.getId() + ":" + dependency.getClassifier() +
971                                     " at " + dependency.getId() + ":" + classifier );
972             }
973 
974             if ( !unavailableArtifactsCache.containsKey( dependency.getId() + ":" + classifier ) )
975             {
976                 Artifact artifact =
977                     IdeUtils.resolveArtifactWithClassifier( dependency.getGroupId(), dependency.getArtifactId(),
978                                                             dependency.getVersion(), classifier, localRepository,
979                                                             artifactResolver, //$NON-NLS-1$
980                                                             artifactFactory, remoteRepos, getLog() );
981                 if ( artifact.isResolved() )
982                 {
983                     if ( "sources".equals( classifier ) )
984                     {
985                         dependency.setSourceAttachment( artifact.getFile() );
986                     }
987                     else if ( "javadoc".equals( classifier ) )
988                     {
989                         dependency.setJavadocAttachment( artifact.getFile() );
990                     }
991                 }
992                 else
993                 {
994                     unavailableArtifactsCache.put( dependency.getId() + ":" + classifier, Boolean.TRUE.toString() );
995                     // add the dependencies to the list
996                     // of those lacking the required
997                     // artifact
998                     missingClassifierDependencies.add( dependency );
999                 }
1000             }
1001         }
1002 
1003         // return the list of dependencies missing the
1004         // required artifact
1005         return missingClassifierDependencies;
1006 
1007     }
1008 
1009     /**
1010      * Output a message with the list of missing dependencies and info on how turn download on if it was disabled.
1011      */
1012     private void reportMissingArtifacts()
1013     {
1014         StringBuffer msg = new StringBuffer();
1015 
1016         if ( !missingSourceDependencies.isEmpty() )
1017         {
1018             if ( getDownloadSources() )
1019             {
1020                 msg.append( Messages.getString( "sourcesnotavailable" ) ); //$NON-NLS-1$
1021             }
1022             else
1023             {
1024                 msg.append( Messages.getString( "sourcesnotdownloaded" ) ); //$NON-NLS-1$
1025             }
1026 
1027             for ( Iterator it = missingSourceDependencies.iterator(); it.hasNext(); )
1028             {
1029                 IdeDependency art = (IdeDependency) it.next();
1030                 msg.append( Messages.getString( "sourcesmissingitem", art.getId() ) ); //$NON-NLS-1$
1031             }
1032             msg.append( "\n" ); //$NON-NLS-1$
1033         }
1034 
1035         if ( !missingJavadocDependencies.isEmpty() )
1036         {
1037             if ( getDownloadJavadocs() )
1038             {
1039                 msg.append( Messages.getString( "javadocnotavailable" ) ); //$NON-NLS-1$
1040             }
1041             else
1042             {
1043                 msg.append( Messages.getString( "javadocnotdownloaded" ) ); //$NON-NLS-1$
1044             }
1045 
1046             for ( Iterator it = missingJavadocDependencies.iterator(); it.hasNext(); )
1047             {
1048                 IdeDependency art = (IdeDependency) it.next();
1049                 msg.append( Messages.getString( "javadocmissingitem", art.getId() ) ); //$NON-NLS-1$
1050             }
1051             msg.append( "\n" ); //$NON-NLS-1$
1052         }
1053         getLog().info( msg ); //$NON-NLS-1$
1054     }
1055 }