View Javadoc

1   package org.ops4j.pax.construct.lifecycle;
2   
3   /*
4    * Copyright 2007 Stuart McCulloch
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * 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, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.io.BufferedWriter;
20  import java.io.File;
21  import java.io.FileInputStream;
22  import java.io.FileOutputStream;
23  import java.io.FileWriter;
24  import java.io.IOException;
25  import java.io.Reader;
26  import java.io.Writer;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Set;
32  import java.util.jar.Attributes;
33  import java.util.jar.Manifest;
34  import java.util.regex.Pattern;
35  
36  import org.apache.maven.artifact.Artifact;
37  import org.apache.maven.plugin.MojoExecutionException;
38  import org.apache.maven.plugin.MojoFailureException;
39  import org.apache.maven.plugin.eclipse.EclipsePlugin;
40  import org.apache.maven.plugin.eclipse.EclipseSourceDir;
41  import org.apache.maven.plugin.eclipse.writers.EclipseClasspathWriter;
42  import org.apache.maven.plugin.eclipse.writers.EclipseProjectWriter;
43  import org.apache.maven.plugin.eclipse.writers.EclipseSettingsWriter;
44  import org.apache.maven.plugin.eclipse.writers.EclipseWriterConfig;
45  import org.apache.maven.plugin.ide.IdeDependency;
46  import org.apache.maven.project.MavenProject;
47  import org.apache.maven.project.MavenProjectBuilder;
48  import org.apache.maven.project.ProjectBuildingException;
49  import org.apache.maven.project.artifact.InvalidDependencyVersionException;
50  import org.apache.maven.shared.osgi.Maven2OsgiConverter;
51  import org.codehaus.plexus.util.FileUtils;
52  import org.codehaus.plexus.util.IOUtil;
53  import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
54  import org.codehaus.plexus.util.xml.Xpp3Dom;
55  import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
56  import org.codehaus.plexus.util.xml.Xpp3DomWriter;
57  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
58  import org.ops4j.pax.construct.util.DirUtils;
59  import org.ops4j.pax.construct.util.DirUtils.EntryFilter;
60  import org.ops4j.pax.construct.util.PomUtils;
61  import org.ops4j.pax.construct.util.ReflectMojo;
62  import org.ops4j.pax.construct.util.StreamFactory;
63  
64  /**
65   * Extends <a href="http://maven.apache.org/plugins/maven-eclipse-plugin/eclipse-mojo.html">EclipsePlugin</a> to
66   * provide customized Eclipse project files for Pax-Construct projects.<br/>Inherited parameters can still be used, but
67   * unfortunately don't appear in the generated docs.
68   * 
69   * @extendsPlugin eclipse
70   * @goal eclipse
71   * @phase package
72   * 
73   * @execute phase="none"
74   */
75  public class EclipseOSGiMojo extends EclipsePlugin
76  {
77      /**
78       * Component factory for Maven projects
79       * 
80       * @component
81       */
82      private MavenProjectBuilder m_mavenProjectBuilder;
83  
84      /**
85       * Component to convert between Maven and OSGi versions
86       * 
87       * @component
88       */
89      private Maven2OsgiConverter m_maven2OsgiConverter;
90  
91      /**
92       * Switch to turn on/off fixing of dependency entries in the classpath file.
93       *
94       * @parameter expression="${fixDependencies}" default-value="EXTERNAL"
95       */
96      private String fixDependencies;
97  
98      /**
99       * Provide access to the private fields of the Eclipse mojo
100      */
101     private ReflectMojo m_eclipseMojo;
102 
103     /**
104      * The main project when generating Eclipse project files for imported bundles
105      */
106     private MavenProject m_provisionProject;
107 
108     /**
109      * IDE dependencies that might be embedded inside the bundle
110      */
111     private List m_embeddableDependencies;
112 
113     /**
114      * {@inheritDoc}
115      */
116     public boolean setup()
117         throws MojoExecutionException
118     {
119         // we don't fork eclipse goal
120         setExecutedProject( project );
121 
122         if( null != m_provisionProject )
123         {
124             enablePDE(); // imported OSGi bundle
125         }
126         else if( PomUtils.isBundleProject( executedProject ) )
127         {
128             enablePDE(); // compiled OSGi bundle
129 
130             setUseProjectReferences( false );
131         }
132         else if( ProvisionMojo.isProvisioningPom( executedProject ) )
133         {
134             try
135             {
136                 /*
137                  * unpack imported bundles and generate Eclipse files one-by-one
138                  */
139                 setupImportedBundles();
140             }
141             catch( InvalidDependencyVersionException e )
142             {
143                 getLog().warn( "Unable to generate Eclipse files for project " + executedProject.getId() );
144             }
145 
146             /*
147              * don't create Eclipse files for the provisioning POM itself!
148              */
149             return false;
150         }
151 
152         // default to normal behaviour
153         return super.setup();
154     }
155 
156     /**
157      * Enable PDE support
158      */
159     private void enablePDE()
160     {
161         if( null == m_eclipseMojo )
162         {
163             // by default enable creation of PDE project files for OSGi
164             m_eclipseMojo = new ReflectMojo( this, EclipsePlugin.class );
165         }
166 
167         m_eclipseMojo.setField( "pde", Boolean.TRUE );
168         setWtpversion( "none" );
169 
170         List containers = getClasspathContainers();
171         if( null != containers && !containers.contains( REQUIRED_PLUGINS_CONTAINER ) )
172         {
173             containers.add( REQUIRED_PLUGINS_CONTAINER );
174         }
175     }
176 
177     /**
178      * {@inheritDoc}
179      */
180     public void writeConfiguration( IdeDependency[] deps )
181         throws MojoExecutionException
182     {
183         if( !isPdeProject() )
184         {
185             // non-OSGi project
186             super.writeConfiguration( deps );
187         }
188         else
189         {
190             m_embeddableDependencies = new ArrayList();
191 
192             if( null == m_provisionProject )
193             {
194                 // compiled OSGi bundle / wrapper
195                 writeBundleConfiguration( deps );
196             }
197             else
198             {
199                 // imported (external) OSGi bundle
200                 writeImportedConfiguration();
201             }
202         }
203     }
204 
205     /**
206      * Customize Eclipse project files for Pax-Construct generated bundles
207      * 
208      * @param deps resolved project dependencies, potentially with sources and javadocs
209      * @throws MojoExecutionException
210      */
211     private void writeBundleConfiguration( IdeDependency[] deps )
212         throws MojoExecutionException
213     {
214         for( int i = 0; i < deps.length; i++ )
215         {
216             if( deps[i].isAddedToClasspath() )
217             {
218                 // cache here so we can attach sources to embedded entries
219                 if( !deps[i].isTestDependency() && !deps[i].isProvided() )
220                 {
221                     m_embeddableDependencies.add( deps[i] );
222                 }
223 
224                 /*
225                  * Change potential test dependencies to be non-OSGi otherwise they won't appear as Required Libraries.
226                  * We include provided dependencies here because PDE might not add them as Plug-in Dependencies, it all
227                  * depends on the manifest (which won't necessarily import packages used during testing)
228                  */
229                 deps[i] = fixOSGiTestDependency( deps[i] );
230             }
231         }
232 
233         EclipseWriterConfig config = createEclipseWriterConfig( deps );
234 
235         config.setEclipseProjectName( getEclipseProjectName( executedProject, true ) );
236 
237         new EclipseSettingsWriter().init( getLog(), config ).write();
238         new EclipseClasspathWriter().init( getLog(), config ).write();
239         new EclipseProjectWriter().init( getLog(), config ).write();
240 
241         /*
242          * copy bundle manifest to where PDE expects it, but tweak it to fix embedded paths
243          */
244         refactorForEclipse( getBundleFile( executedProject ) );
245 
246         writeAdditionalConfig();
247     }
248 
249     /**
250      * @param bundleProject bundle project
251      * @return recently built bundle
252      */
253     private File getBundleFile( MavenProject bundleProject )
254     {
255         Artifact artifact = bundleProject.getArtifact();
256         File bundleFile = artifact.getFile();
257 
258         if( null == bundleFile || !bundleFile.exists() )
259         {
260             // no file attached in this cycle, so check local build
261             String name = bundleProject.getBuild().getFinalName() + ".jar";
262             bundleFile = new File( bundleProject.getBuild().getDirectory(), name );
263         }
264 
265         if( !bundleFile.exists() )
266         {
267             // last chance: see if it is already has been installed locally
268             PomUtils.getFile( artifact, artifactResolver, localRepository );
269             bundleFile = artifact.getFile();
270         }
271 
272         return bundleFile;
273     }
274 
275     /**
276      * The maven-eclipse-plugin has a bug where test dependencies that are also OSGi bundles are excluded from the
277      * .classpath because they are expected to be on the Bundle-ClassPath. However, this is not a valid assumption
278      * because the Maven test-cases and their dependencies do not necessarily end-up packaged inside the bundle.
279      * 
280      * @param dependency an IDE test dependency
281      * @return fixed IDE test dependency
282      */
283     private IdeDependency fixOSGiTestDependency( IdeDependency dependency )
284     {
285         if( "FALSE".equalsIgnoreCase( fixDependencies ) )
286         {
287             return dependency;
288         }
289 
290         // by default, don't fix dependencies pointing to local projects found in the same tree
291         if( "EXTERNAL".equalsIgnoreCase( fixDependencies ) && isReactorDependency( dependency ) )
292         {
293             return dependency;
294         }
295 
296         // previous behaviour in 1.3 was exhaustive search of the poms in the local file-system
297         if( "CUSTOM".equalsIgnoreCase( fixDependencies ) )
298         {
299             String id = dependency.getGroupId() + ':' + dependency.getArtifactId();
300             File baseDir = executedProject.getBasedir();
301 
302             if( DirUtils.findPom( baseDir, id ) != null )
303             {
304                 return dependency;
305             }
306         }
307 
308         // unfortunately there's no setIsOsgiBundle() method, so we have to replace the whole dependency...
309         IdeDependency testDependency = new IdeDependency( dependency.getGroupId(), dependency.getArtifactId(),
310             dependency.getVersion(), dependency.getClassifier(), dependency.isReferencedProject(), true, false, false,
311             dependency.isAddedToClasspath(), dependency.getFile(), dependency.getType(), false, null, 0,
312             dependency.getEclipseProjectName() );
313 
314         testDependency.setSourceAttachment( dependency.getSourceAttachment() );
315         testDependency.setJavadocAttachment( dependency.getJavadocAttachment() );
316 
317         return testDependency;
318     }
319 
320     private boolean isReactorDependency( IdeDependency dependency )
321     {
322         // check current reactor...
323         if( reactorProjects != null )
324         {
325             for( Iterator i = reactorProjects.iterator(); i.hasNext(); )
326             {
327                 MavenProject reactorProject = (MavenProject) i.next();
328 
329                 if( reactorProject.getGroupId().equals( dependency.getGroupId() )
330                     && reactorProject.getArtifactId().equals( dependency.getArtifactId() ) )
331                 {
332                     return true;
333                 }
334             }
335         }
336         return false;
337     }
338 
339     /**
340      * Provide better naming for Pax-Construct generated OSGi bundles
341      * 
342      * @param project current Maven project
343      * @param addVersion when true, add the project version to the name
344      * @return an Eclipse friendly name for the bundle
345      */
346     private static String getEclipseProjectName( MavenProject project, boolean addVersion )
347     {
348         String projectName = project.getProperties().getProperty( "bundle.symbolicName" );
349         if( null == projectName )
350         {
351             // fall back to standard "groupId.artifactId" but try to eliminate duplicate segments
352             projectName = PomUtils.getCompoundId( project.getGroupId(), project.getArtifactId() );
353         }
354 
355         if( addVersion )
356         {
357             // check for wrapper version, which reflects the version of the wrapped contents
358             String projectVersion = project.getProperties().getProperty( "wrapped.version" );
359             if( null == projectVersion )
360             {
361                 projectVersion = project.getVersion();
362             }
363 
364             return projectName + " [" + projectVersion + ']';
365         }
366 
367         return projectName;
368     }
369 
370     /**
371      * Select files for unpacking that don't exist locally under target/classes
372      */
373     private static class IncludedContentFilter
374         implements EntryFilter
375     {
376         private final File m_outputDir;
377 
378         /**
379          * @param outputDir build output directory
380          */
381         public IncludedContentFilter( File outputDir )
382         {
383             m_outputDir = outputDir;
384         }
385 
386         /**
387          * {@inheritDoc}
388          */
389         public boolean accept( String name )
390         {
391             // always select metadata folders as we may need to refactor them
392             if( name.startsWith( "META-INF" ) || name.startsWith( "OSGI-INF" ) )
393             {
394                 return true;
395             }
396             // also select any embedded jars
397             else if( name.endsWith( ".jar" ) )
398             {
399                 return true;
400             }
401 
402             // do we already have this file locally?
403             return new File( m_outputDir, name ).exists() == false;
404         }
405     }
406 
407     /**
408      * Copy OSGi metadata to where Eclipse PDE expects it, but adjust the Bundle-ClassPath so Eclipse can find any
409      * embedded jars or directories in the unpacked bundle contents under the temporary directory
410      * 
411      * @param bundleFile the packaged bundle
412      */
413     private void refactorForEclipse( File bundleFile )
414     {
415         // temporary location in the output folder
416         String tempPath = "target/pax-eclipse";
417         boolean refactorManifest = false;
418 
419         // make relative to the provisioning POM
420         File baseDir = executedProject.getBasedir();
421         File unpackDir = new File( baseDir, tempPath );
422 
423         if( bundleFile == null || !bundleFile.exists() )
424         {
425             getLog().warn( "Bundle has not been built, reverting to basic behaviour" );
426         }
427         else
428         {
429             DirUtils.unpackBundle( bundleFile, unpackDir, new IncludedContentFilter( getBuildOutputDirectory() ) );
430 
431             moveMetadata( unpackDir, "META-INF", baseDir );
432             moveMetadata( unpackDir, "OSGI-INF", baseDir );
433 
434             // test to see if it's empty
435             unpackDir.delete();
436 
437             refactorManifest = unpackDir.exists();
438         }
439 
440         File manifestFile = new File( baseDir, "META-INF/MANIFEST.MF" );
441 
442         Manifest manifest = getBundleManifest( manifestFile );
443         Attributes mainAttributes = manifest.getMainAttributes();
444 
445         if( mainAttributes.getValue( "Bundle-SymbolicName" ) == null )
446         {
447             // Eclipse mis-behaves if the bundle has no symbolic name :(
448             String name = getEclipseProjectName( executedProject, false );
449             mainAttributes.putValue( "Bundle-SymbolicName", name.replace( '-', '_' ) );
450         }
451 
452         String bundleClassPath = mainAttributes.getValue( "Bundle-ClassPath" );
453 
454         /*
455          * refactor Bundle-ClassPath to help Eclipse find the unpacked contents
456          */
457         if( refactorManifest )
458         {
459             bundleClassPath = ".," + DirUtils.rebasePaths( bundleClassPath, tempPath, ',' );
460             mainAttributes.putValue( "Bundle-ClassPath", bundleClassPath );
461 
462             // add the embedded entries back to the Eclipse classpath
463             addEmbeddedEntriesToEclipseClassPath( tempPath, bundleClassPath );
464         }
465 
466         try
467         {
468             manifestFile.getParentFile().mkdirs();
469             FileOutputStream out = new FileOutputStream( manifestFile );
470             manifest.write( out );
471             IOUtil.close( out );
472         }
473         catch( IOException e )
474         {
475             getLog().warn( "Unable to update Eclipse manifest: " + manifestFile );
476         }
477 
478         createBuildProperties( baseDir, tempPath );
479     }
480 
481     /**
482      * Create simple build.properties file so the export functionality from Eclipse/PDE works
483      * 
484      * @param baseDir project base directory
485      * @param unpackPath path where the additional bundle contents were unpacked
486      */
487     private void createBuildProperties( File baseDir, String unpackPath )
488     {
489         File buildPropertiesFile = new File( baseDir, "build.properties" );
490         if( !buildPropertiesFile.exists() )
491         {
492             BufferedWriter writer = null;
493             try
494             {
495                 writer = new BufferedWriter( new FileWriter( buildPropertiesFile ) );
496 
497                 /* compiled/wrapped bundle */
498                 if( null != unpackPath )
499                 {
500                     File sourceDir = new File( baseDir, "src/main/java" );
501                     if( sourceDir.exists() )
502                     {
503                         writer.write( "source.. = src/main/java/,src/main/resources/" );
504                         writer.newLine();
505                     }
506 
507                     writer.write( "output.. = target/classes/" );
508                     writer.newLine();
509                     writer.write( "bin.includes = META-INF/,." );
510                     if( new File( baseDir, unpackPath ).exists() )
511                     {
512                         writer.write( ',' + unpackPath + '/' );
513                     }
514                     writer.newLine();
515                 }
516                 /* imported bundle */
517                 else
518                 {
519                     File bundleFile = executedProject.getArtifact().getFile();
520                     if( null != bundleFile && bundleFile.isFile() )
521                     {
522                         writer.write( "install.location = " + bundleFile.toURI() );
523                         writer.newLine();
524                     }
525                     writer.write( "source.. = ." );
526                     writer.newLine();
527                     writer.write( "output.. = ." );
528                     writer.newLine();
529                     writer.write( "bin.includes = META-INF/,." );
530                     writer.newLine();
531                 }
532             }
533             catch( IOException e )
534             {
535                 getLog().warn( "Unable to create build.properties file" );
536             }
537             finally
538             {
539                 IOUtil.close( writer );
540             }
541         }
542     }
543 
544     /**
545      * @param manifestFile path to the local bundle manifest
546      * @return the bundle manifest, or sane defaults if the manifest couldn't be opened
547      */
548     private Manifest getBundleManifest( File manifestFile )
549     {
550         Manifest manifest = new Manifest();
551 
552         try
553         {
554             FileInputStream in = new FileInputStream( manifestFile );
555             manifest.read( in );
556             IOUtil.close( in );
557         }
558         catch( IOException e )
559         {
560             Attributes mainAttributes = manifest.getMainAttributes();
561 
562             String osgiVersion = m_maven2OsgiConverter.getVersion( project.getVersion() );
563 
564             // standard OSGi entries
565             mainAttributes.putValue( "Manifest-Version", "1" );
566             mainAttributes.putValue( "Bundle-ManifestVersion", "2" );
567             mainAttributes.putValue( "Bundle-Name", project.getName() );
568             mainAttributes.putValue( "Bundle-Version", osgiVersion );
569 
570             // some basic OSGi dependencies, to help people get their code compiling...
571             mainAttributes.putValue( "Import-Package", "org.osgi.framework,org.osgi.util.tracker" );
572         }
573 
574         return manifest;
575     }
576 
577     /**
578      * @param fromDir source directory
579      * @param metadata metadata folder
580      * @param toDir target directory
581      */
582     private void moveMetadata( File fromDir, String metadata, File toDir )
583     {
584         File metadataDir = new File( fromDir, metadata );
585         if( metadataDir.exists() )
586         {
587             try
588             {
589                 FileUtils.copyDirectoryStructure( metadataDir, new File( toDir, metadata ) );
590                 FileUtils.deleteDirectory( metadataDir );
591             }
592             catch( IOException e )
593             {
594                 getLog().warn( "Unable to copy " + metadata + " contents to base directory" );
595             }
596         }
597     }
598 
599     /**
600      * Add any embedded Bundle-ClassPath entries to the Eclipse classpath and re-attach sources/javadocs
601      * 
602      * @param bundleLocation relative path to the unpacked bundle
603      * @param bundleClassPath the refactored Bundle-ClassPath
604      */
605     private void addEmbeddedEntriesToEclipseClassPath( String bundleLocation, String bundleClassPath )
606     {
607         String[] classPath = bundleClassPath.split( "," );
608         File basedir = executedProject.getBasedir();
609 
610         try
611         {
612             File classPathFile = new File( basedir, ".classpath" );
613             Reader reader = StreamFactory.newXmlReader( classPathFile );
614             Xpp3Dom classPathXML = Xpp3DomBuilder.build( reader );
615             IOUtil.close( reader );
616 
617             for( int i = 0; i < classPath.length; i++ )
618             {
619                 String binaryPath = classPath[i].trim();
620 
621                 if( !".".equals( binaryPath ) && new File( basedir, binaryPath ).exists() )
622                 {
623                     // embedded jar/directory needs to be a 'lib' entry
624                     Xpp3Dom classPathEntry = new Xpp3Dom( "classpathentry" );
625                     classPathEntry.setAttribute( "exported", "true" );
626                     classPathEntry.setAttribute( "kind", "lib" );
627                     classPathEntry.setAttribute( "path", binaryPath );
628 
629                     // find attached sources using the previously cached IDE dependencies
630                     File sourcePath = findAttachedSource( bundleLocation, binaryPath );
631                     if( sourcePath != null )
632                     {
633                         classPathEntry.setAttribute( "sourcepath", sourcePath.getPath() );
634                     }
635 
636                     classPathXML.addChild( classPathEntry );
637                 }
638             }
639 
640             Writer writer = StreamFactory.newXmlWriter( classPathFile );
641             Xpp3DomWriter.write( new PrettyPrintXMLWriter( writer ), classPathXML );
642             IOUtil.close( writer );
643         }
644         catch( IOException e )
645         {
646             getLog().warn( "Unable to find Eclipse .classpath file" );
647         }
648         catch( XmlPullParserException e )
649         {
650             getLog().warn( "Unable to parse Eclipse .classpath file" );
651         }
652     }
653 
654     /**
655      * Search cached IDE dependencies for an entry matching the given classpath element
656      * 
657      * @param bundleLocation relative path to the unpacked bundle
658      * @param classPathEntry classpath element
659      * @return path to the attached sources
660      */
661     private File findAttachedSource( String bundleLocation, String classPathEntry )
662     {
663         for( Iterator i = m_embeddableDependencies.iterator(); i.hasNext(); )
664         {
665             IdeDependency dependency = (IdeDependency) i.next();
666 
667             // equivalent to '.' - source is first in list
668             if( bundleLocation.equals( classPathEntry ) )
669             {
670                 return dependency.getSourceAttachment();
671             }
672             else if( Pattern.matches( "^.*[/\\\\]" + dependency.getArtifactId() + "[-.][^/\\\\]*$", classPathEntry ) )
673             {
674                 return dependency.getSourceAttachment();
675             }
676         }
677 
678         return null;
679     }
680 
681     /**
682      * Unpack each imported bundle in turn and generate the relevant Eclipse project files
683      * 
684      * @throws InvalidDependencyVersionException
685      * @throws MojoExecutionException
686      */
687     private void setupImportedBundles()
688         throws InvalidDependencyVersionException,
689         MojoExecutionException
690     {
691         // don't process dependencies of imported bundles
692         m_provisionProject = getExecutedProject();
693         setResolveDependencies( false );
694 
695         Set artifacts = m_provisionProject.createArtifacts( artifactFactory, null, null );
696         for( Iterator i = artifacts.iterator(); i.hasNext(); )
697         {
698             Artifact artifact = (Artifact) i.next();
699 
700             // store project locally underneath the provisioning POM's directory
701             File groupDir = new File( m_provisionProject.getBasedir(), "target/" + artifact.getGroupId() );
702             File baseDir = new File( groupDir, artifact.getArtifactId() + '-' + artifact.getVersion() );
703 
704             // download and unpack the bundle
705             if( !PomUtils.downloadFile( artifact, artifactResolver, remoteArtifactRepositories, localRepository ) )
706             {
707                 getLog().warn( "Skipping missing bundle " + artifact );
708                 continue;
709             }
710 
711             DirUtils.unpackBundle( artifact.getFile(), baseDir, null );
712 
713             // download the bundle POM and store locally
714             MavenProject dependencyProject = writeProjectPom( baseDir, artifact );
715             if( null == dependencyProject )
716             {
717                 getLog().warn( "Skipping missing bundle " + artifact );
718                 continue;
719             }
720 
721             dependencyProject.setArtifact( artifact );
722 
723             setExecutedProject( dependencyProject );
724             setProject( dependencyProject );
725 
726             // trick Eclipse plugin to do the right thing
727             setBuildOutputDirectory( new File( baseDir, ".ignore" ) );
728             setEclipseProjectDir( baseDir );
729 
730             try
731             {
732                 // call the Eclipse plugin
733                 getLog().info( "Generating Eclipse project for bundle " + artifact );
734                 execute();
735             }
736             catch( MojoFailureException e )
737             {
738                 getLog().warn( "Problem generating Eclipse files for artifact " + artifact );
739             }
740         }
741     }
742 
743     /**
744      * Download and save the Maven POM for the given artifact
745      * 
746      * @param baseDir base directory
747      * @param artifact Maven artifact
748      * @return the downloaded project
749      */
750     private MavenProject writeProjectPom( File baseDir, Artifact artifact )
751     {
752         MavenProject pom = null;
753 
754         String groupId = artifact.getGroupId();
755         String artifactId = artifact.getArtifactId();
756         String version = artifact.getVersion();
757 
758         Artifact pomArtifact = artifactFactory.createProjectArtifact( groupId, artifactId, version );
759 
760         try
761         {
762             pom = m_mavenProjectBuilder.buildFromRepository( pomArtifact, remoteArtifactRepositories, localRepository );
763 
764             File pomFile = new File( baseDir, "pom.xml" );
765 
766             Writer writer = StreamFactory.newXmlWriter( pomFile );
767             pom.writeModel( writer );
768             pom.setFile( pomFile );
769             IOUtil.close( writer );
770         }
771         catch( ProjectBuildingException e )
772         {
773             getLog().warn( "Unable to build POM for artifact " + pomArtifact );
774         }
775         catch( IOException e )
776         {
777             getLog().warn( "Unable to write POM for artifact " + pomArtifact );
778         }
779 
780         return pom;
781     }
782 
783     /**
784      * Customize Eclipse project files for imported (unpacked) bundles
785      * 
786      * @throws MojoExecutionException
787      */
788     private void writeImportedConfiguration()
789         throws MojoExecutionException
790     {
791         EclipseWriterConfig config = createEclipseWriterConfig( new IdeDependency[0] );
792         config.setEclipseProjectName( getEclipseProjectName( executedProject, true ) );
793         config.setClasspathContainers( Collections.EMPTY_LIST );
794         config.setSourceDirs( new EclipseSourceDir[0] );
795 
796         // not compiling, so just need project and classpath files
797         new EclipseClasspathWriter().init( getLog(), config ).write();
798         new EclipseProjectWriter().init( getLog(), config ).write();
799 
800         Artifact sourceArtifact = artifactFactory.createArtifactWithClassifier( executedProject.getGroupId(),
801             executedProject.getArtifactId(), executedProject.getVersion(), "java-source", "sources" );
802 
803         if( downloadSources )
804         {
805             PomUtils.downloadFile( sourceArtifact, artifactResolver, remoteArtifactRepositories, localRepository );
806         }
807         else
808         {
809             // check to see if we already have the source downloaded...
810             PomUtils.getFile( sourceArtifact, artifactResolver, localRepository );
811         }
812 
813         // set PDE classpath to point to unpacked bundle
814         attachImportedContent( sourceArtifact.getFile() );
815 
816         String baseDir = executedProject.getBasedir().getPath();
817         File manifestFile = new File( baseDir, "META-INF/MANIFEST.MF" );
818 
819         Manifest manifest = getBundleManifest( manifestFile );
820         Attributes mainAttributes = manifest.getMainAttributes();
821 
822         String bundleClassPath = mainAttributes.getValue( "Bundle-ClassPath" );
823         if( null != bundleClassPath )
824         {
825             // add any embedded entries to the default Eclipse classpath
826             addEmbeddedEntriesToEclipseClassPath( baseDir, bundleClassPath );
827         }
828 
829         createBuildProperties( new File( baseDir ), null );
830     }
831 
832     /**
833      * Add a classpath entry for the unpacked imported bundle and attach it to the given source
834      * 
835      * @param sources attached bundle sources
836      */
837     private void attachImportedContent( File sources )
838     {
839         try
840         {
841             File classPathFile = new File( executedProject.getBasedir(), ".classpath" );
842             Reader reader = StreamFactory.newXmlReader( classPathFile );
843             Xpp3Dom classPathXML = Xpp3DomBuilder.build( reader );
844             IOUtil.close( reader );
845 
846             Xpp3Dom classPathEntry = new Xpp3Dom( "classpathentry" );
847             classPathEntry.setAttribute( "exported", "true" );
848             classPathEntry.setAttribute( "kind", "lib" );
849             classPathEntry.setAttribute( "path", "." );
850             if( sources != null && sources.exists() )
851             {
852                 classPathEntry.setAttribute( "sourcepath", sources.getPath() );
853             }
854             classPathXML.addChild( classPathEntry );
855 
856             Writer writer = StreamFactory.newXmlWriter( classPathFile );
857             Xpp3DomWriter.write( new PrettyPrintXMLWriter( writer ), classPathXML );
858             IOUtil.close( writer );
859         }
860         catch( IOException e )
861         {
862             getLog().warn( "Unable to find Eclipse .classpath file" );
863         }
864         catch( XmlPullParserException e )
865         {
866             getLog().warn( "Unable to parse Eclipse .classpath file" );
867         }
868     }
869 }