1 package org.ops4j.pax.construct.lifecycle;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
66
67
68
69
70
71
72
73
74
75 public class EclipseOSGiMojo extends EclipsePlugin
76 {
77
78
79
80
81
82 private MavenProjectBuilder m_mavenProjectBuilder;
83
84
85
86
87
88
89 private Maven2OsgiConverter m_maven2OsgiConverter;
90
91
92
93
94
95
96 private String fixDependencies;
97
98
99
100
101 private ReflectMojo m_eclipseMojo;
102
103
104
105
106 private MavenProject m_provisionProject;
107
108
109
110
111 private List m_embeddableDependencies;
112
113
114
115
116 public boolean setup()
117 throws MojoExecutionException
118 {
119
120 setExecutedProject( project );
121
122 if( null != m_provisionProject )
123 {
124 enablePDE();
125 }
126 else if( PomUtils.isBundleProject( executedProject ) )
127 {
128 enablePDE();
129
130 setUseProjectReferences( false );
131 }
132 else if( ProvisionMojo.isProvisioningPom( executedProject ) )
133 {
134 try
135 {
136
137
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
148
149 return false;
150 }
151
152
153 return super.setup();
154 }
155
156
157
158
159 private void enablePDE()
160 {
161 if( null == m_eclipseMojo )
162 {
163
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
179
180 public void writeConfiguration( IdeDependency[] deps )
181 throws MojoExecutionException
182 {
183 if( !isPdeProject() )
184 {
185
186 super.writeConfiguration( deps );
187 }
188 else
189 {
190 m_embeddableDependencies = new ArrayList();
191
192 if( null == m_provisionProject )
193 {
194
195 writeBundleConfiguration( deps );
196 }
197 else
198 {
199
200 writeImportedConfiguration();
201 }
202 }
203 }
204
205
206
207
208
209
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
219 if( !deps[i].isTestDependency() && !deps[i].isProvided() )
220 {
221 m_embeddableDependencies.add( deps[i] );
222 }
223
224
225
226
227
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
243
244 refactorForEclipse( getBundleFile( executedProject ) );
245
246 writeAdditionalConfig();
247 }
248
249
250
251
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
261 String name = bundleProject.getBuild().getFinalName() + ".jar";
262 bundleFile = new File( bundleProject.getBuild().getDirectory(), name );
263 }
264
265 if( !bundleFile.exists() )
266 {
267
268 PomUtils.getFile( artifact, artifactResolver, localRepository );
269 bundleFile = artifact.getFile();
270 }
271
272 return bundleFile;
273 }
274
275
276
277
278
279
280
281
282
283 private IdeDependency fixOSGiTestDependency( IdeDependency dependency )
284 {
285 if( "FALSE".equalsIgnoreCase( fixDependencies ) )
286 {
287 return dependency;
288 }
289
290
291 if( "EXTERNAL".equalsIgnoreCase( fixDependencies ) && isReactorDependency( dependency ) )
292 {
293 return dependency;
294 }
295
296
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
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
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
341
342
343
344
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
352 projectName = PomUtils.getCompoundId( project.getGroupId(), project.getArtifactId() );
353 }
354
355 if( addVersion )
356 {
357
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
372
373 private static class IncludedContentFilter
374 implements EntryFilter
375 {
376 private final File m_outputDir;
377
378
379
380
381 public IncludedContentFilter( File outputDir )
382 {
383 m_outputDir = outputDir;
384 }
385
386
387
388
389 public boolean accept( String name )
390 {
391
392 if( name.startsWith( "META-INF" ) || name.startsWith( "OSGI-INF" ) )
393 {
394 return true;
395 }
396
397 else if( name.endsWith( ".jar" ) )
398 {
399 return true;
400 }
401
402
403 return new File( m_outputDir, name ).exists() == false;
404 }
405 }
406
407
408
409
410
411
412
413 private void refactorForEclipse( File bundleFile )
414 {
415
416 String tempPath = "target/pax-eclipse";
417 boolean refactorManifest = false;
418
419
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
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
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
456
457 if( refactorManifest )
458 {
459 bundleClassPath = ".," + DirUtils.rebasePaths( bundleClassPath, tempPath, ',' );
460 mainAttributes.putValue( "Bundle-ClassPath", bundleClassPath );
461
462
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
483
484
485
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
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
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
546
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
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
571 mainAttributes.putValue( "Import-Package", "org.osgi.framework,org.osgi.util.tracker" );
572 }
573
574 return manifest;
575 }
576
577
578
579
580
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
601
602
603
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
624 Xpp3Dom classPathEntry = new Xpp3Dom( "classpathentry" );
625 classPathEntry.setAttribute( "exported", "true" );
626 classPathEntry.setAttribute( "kind", "lib" );
627 classPathEntry.setAttribute( "path", binaryPath );
628
629
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
656
657
658
659
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
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
683
684
685
686
687 private void setupImportedBundles()
688 throws InvalidDependencyVersionException,
689 MojoExecutionException
690 {
691
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
701 File groupDir = new File( m_provisionProject.getBasedir(), "target/" + artifact.getGroupId() );
702 File baseDir = new File( groupDir, artifact.getArtifactId() + '-' + artifact.getVersion() );
703
704
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
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
727 setBuildOutputDirectory( new File( baseDir, ".ignore" ) );
728 setEclipseProjectDir( baseDir );
729
730 try
731 {
732
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
745
746
747
748
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
785
786
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
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
810 PomUtils.getFile( sourceArtifact, artifactResolver, localRepository );
811 }
812
813
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
826 addEmbeddedEntriesToEclipseClassPath( baseDir, bundleClassPath );
827 }
828
829 createBuildProperties( new File( baseDir ), null );
830 }
831
832
833
834
835
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 }