1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.felix.bundleplugin;
20
21
22 import java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.lang.reflect.Array;
29 import java.lang.reflect.Method;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collection;
33 import java.util.Collections;
34 import java.util.Enumeration;
35 import java.util.HashSet;
36 import java.util.Iterator;
37 import java.util.LinkedHashMap;
38 import java.util.LinkedHashSet;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Properties;
42 import java.util.Set;
43 import java.util.TreeSet;
44 import java.util.jar.Attributes;
45 import java.util.jar.Manifest;
46
47 import org.apache.maven.archiver.ManifestSection;
48 import org.apache.maven.archiver.MavenArchiveConfiguration;
49 import org.apache.maven.archiver.MavenArchiver;
50 import org.apache.maven.artifact.Artifact;
51 import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
52 import org.apache.maven.execution.MavenSession;
53 import org.apache.maven.model.License;
54 import org.apache.maven.model.Model;
55 import org.apache.maven.model.Resource;
56 import org.apache.maven.plugin.AbstractMojo;
57 import org.apache.maven.plugin.MojoExecutionException;
58 import org.apache.maven.plugin.MojoFailureException;
59 import org.apache.maven.plugin.logging.Log;
60 import org.apache.maven.project.MavenProject;
61 import org.apache.maven.project.MavenProjectHelper;
62 import org.apache.maven.shared.osgi.DefaultMaven2OsgiConverter;
63 import org.apache.maven.shared.osgi.Maven2OsgiConverter;
64 import org.codehaus.plexus.archiver.UnArchiver;
65 import org.codehaus.plexus.archiver.manager.ArchiverManager;
66 import org.codehaus.plexus.util.DirectoryScanner;
67 import org.codehaus.plexus.util.FileUtils;
68 import org.codehaus.plexus.util.StringUtils;
69
70 import aQute.lib.osgi.Analyzer;
71 import aQute.lib.osgi.Builder;
72 import aQute.lib.osgi.Constants;
73 import aQute.lib.osgi.EmbeddedResource;
74 import aQute.lib.osgi.FileResource;
75 import aQute.lib.osgi.Jar;
76 import aQute.lib.osgi.Processor;
77 import aQute.lib.spring.SpringXMLType;
78
79
80
81
82
83
84
85
86
87
88
89 public class BundlePlugin extends AbstractMojo
90 {
91
92
93
94
95
96 protected File manifestLocation;
97
98
99
100
101
102
103 protected File dumpInstructions;
104
105
106
107
108
109
110 protected File dumpClasspath;
111
112
113
114
115
116
117 protected boolean unpackBundle;
118
119
120
121
122
123
124 protected String excludeDependencies;
125
126
127
128
129
130
131
132 protected String classifier;
133
134
135
136
137 private MavenProjectHelper m_projectHelper;
138
139
140
141
142 private ArchiverManager m_archiverManager;
143
144
145
146
147 private ArtifactHandlerManager m_artifactHandlerManager;
148
149
150
151
152
153
154 protected List supportedProjectTypes = Arrays.asList( new String[]
155 { "jar", "bundle" } );
156
157
158
159
160
161
162
163 private File outputDirectory;
164
165
166
167
168
169
170
171 private String buildDirectory;
172
173
174
175
176
177
178
179
180 private MavenProject project;
181
182
183
184
185
186
187 private Map instructions = new LinkedHashMap();
188
189
190
191
192 private Maven2OsgiConverter m_maven2OsgiConverter = new DefaultMaven2OsgiConverter();
193
194
195
196
197
198
199 private MavenArchiveConfiguration archive;
200
201
202
203
204
205
206 private MavenSession m_mavenSession;
207
208 private static final String MAVEN_SYMBOLICNAME = "maven-symbolicname";
209 private static final String MAVEN_RESOURCES = "{maven-resources}";
210 private static final String LOCAL_PACKAGES = "{local-packages}";
211 private static final String MAVEN_SOURCES = "{maven-sources}";
212
213 private static final String[] EMPTY_STRING_ARRAY =
214 {};
215 private static final String[] DEFAULT_INCLUDES =
216 { "**/**" };
217
218 private static final String NL = System.getProperty( "line.separator" );
219
220
221 protected Maven2OsgiConverter getMaven2OsgiConverter()
222 {
223 return m_maven2OsgiConverter;
224 }
225
226
227 protected void setMaven2OsgiConverter( Maven2OsgiConverter maven2OsgiConverter )
228 {
229 m_maven2OsgiConverter = maven2OsgiConverter;
230 }
231
232
233 protected MavenProject getProject()
234 {
235 return project;
236 }
237
238
239
240
241
242 public void execute() throws MojoExecutionException
243 {
244 Properties properties = new Properties();
245 String projectType = getProject().getArtifact().getType();
246
247
248 if ( !supportedProjectTypes.contains( projectType ) )
249 {
250 getLog().warn(
251 "Ignoring project type " + projectType + " - supportedProjectTypes = " + supportedProjectTypes );
252 return;
253 }
254
255 execute( getProject(), instructions, properties );
256 }
257
258
259 protected void execute( MavenProject currentProject, Map originalInstructions, Properties properties )
260 throws MojoExecutionException
261 {
262 try
263 {
264 execute( currentProject, originalInstructions, properties, getClasspath( currentProject ) );
265 }
266 catch ( IOException e )
267 {
268 throw new MojoExecutionException( "Error calculating classpath for project " + currentProject, e );
269 }
270 }
271
272
273
274 protected static Map transformDirectives( Map originalInstructions )
275 {
276 Map transformedInstructions = new LinkedHashMap();
277 for ( Iterator i = originalInstructions.entrySet().iterator(); i.hasNext(); )
278 {
279 Map.Entry e = ( Map.Entry ) i.next();
280
281 String key = ( String ) e.getKey();
282 if ( key.startsWith( "_" ) )
283 {
284 key = "-" + key.substring( 1 );
285 }
286
287 String value = ( String ) e.getValue();
288 if ( null == value )
289 {
290 value = "";
291 }
292 else
293 {
294 value = value.replaceAll( "\\p{Blank}*[\r\n]\\p{Blank}*", "" );
295 }
296
297 if ( Analyzer.WAB.equals( key ) && value.length() == 0 )
298 {
299
300 value = "src/main/webapp/";
301 }
302
303 transformedInstructions.put( key, value );
304 }
305 return transformedInstructions;
306 }
307
308
309 protected boolean reportErrors( String prefix, Analyzer analyzer )
310 {
311 List errors = analyzer.getErrors();
312 List warnings = analyzer.getWarnings();
313
314 for ( Iterator w = warnings.iterator(); w.hasNext(); )
315 {
316 String msg = ( String ) w.next();
317 getLog().warn( prefix + " : " + msg );
318 }
319
320 boolean hasErrors = false;
321 String fileNotFound = "Input file does not exist: ";
322 for ( Iterator e = errors.iterator(); e.hasNext(); )
323 {
324 String msg = ( String ) e.next();
325 if ( msg.startsWith( fileNotFound ) && msg.endsWith( "~" ) )
326 {
327
328 String duplicate = Processor.removeDuplicateMarker( msg.substring( fileNotFound.length() ) );
329 getLog().warn( prefix + " : Duplicate path '" + duplicate + "' in Include-Resource" );
330 }
331 else
332 {
333 getLog().error( prefix + " : " + msg );
334 hasErrors = true;
335 }
336 }
337 return hasErrors;
338 }
339
340
341 protected void execute( MavenProject currentProject, Map originalInstructions, Properties properties,
342 Jar[] classpath ) throws MojoExecutionException
343 {
344 try
345 {
346 File jarFile = new File( getBuildDirectory(), getBundleName( currentProject ) );
347 Builder builder = buildOSGiBundle( currentProject, originalInstructions, properties, classpath );
348 boolean hasErrors = reportErrors( "Bundle " + currentProject.getArtifact(), builder );
349 if ( hasErrors )
350 {
351 String failok = builder.getProperty( "-failok" );
352 if ( null == failok || "false".equalsIgnoreCase( failok ) )
353 {
354 jarFile.delete();
355
356 throw new MojoFailureException( "Error(s) found in bundle configuration" );
357 }
358 }
359
360
361 jarFile.getParentFile().mkdirs();
362 builder.getJar().write( jarFile );
363
364 Artifact mainArtifact = currentProject.getArtifact();
365
366 if ( "bundle".equals( mainArtifact.getType() ) )
367 {
368
369 mainArtifact.setArtifactHandler( m_artifactHandlerManager.getArtifactHandler( "jar" ) );
370 }
371
372 if ( null == classifier || classifier.trim().length() == 0 )
373 {
374 mainArtifact.setFile( jarFile );
375 }
376 else
377 {
378 m_projectHelper.attachArtifact( currentProject, jarFile, classifier );
379 }
380
381 if ( unpackBundle )
382 {
383 unpackBundle( jarFile );
384 }
385
386 if ( manifestLocation != null )
387 {
388 File outputFile = new File( manifestLocation, "MANIFEST.MF" );
389
390 try
391 {
392 Manifest manifest = builder.getJar().getManifest();
393 ManifestPlugin.writeManifest( manifest, outputFile );
394 }
395 catch ( IOException e )
396 {
397 getLog().error( "Error trying to write Manifest to file " + outputFile, e );
398 }
399 }
400
401
402 builder.close();
403 }
404 catch ( MojoFailureException e )
405 {
406 getLog().error( e.getLocalizedMessage() );
407 throw new MojoExecutionException( "Error(s) found in bundle configuration", e );
408 }
409 catch ( Exception e )
410 {
411 getLog().error( "An internal error occurred", e );
412 throw new MojoExecutionException( "Internal error in maven-bundle-plugin", e );
413 }
414 }
415
416
417 protected Builder getOSGiBuilder( MavenProject currentProject, Map originalInstructions, Properties properties,
418 Jar[] classpath ) throws Exception
419 {
420 properties.putAll( getDefaultProperties( currentProject ) );
421 properties.putAll( transformDirectives( originalInstructions ) );
422
423 Builder builder = new Builder();
424 builder.setBase( getBase( currentProject ) );
425 builder.setProperties( sanitize( properties ) );
426 if ( classpath != null )
427 {
428 builder.setClasspath( classpath );
429 }
430
431 return builder;
432 }
433
434
435 protected static Properties sanitize( Properties properties )
436 {
437
438 Properties sanitizedEntries = new Properties();
439 for ( Iterator itr = properties.entrySet().iterator(); itr.hasNext(); )
440 {
441 Map.Entry entry = ( Map.Entry ) itr.next();
442 if ( entry.getKey() instanceof String == false )
443 {
444 String key = sanitize( entry.getKey() );
445 if ( !properties.containsKey( key ) )
446 {
447 sanitizedEntries.setProperty( key, sanitize( entry.getValue() ) );
448 }
449 itr.remove();
450 }
451 else if ( entry.getValue() instanceof String == false )
452 {
453 entry.setValue( sanitize( entry.getValue() ) );
454 }
455 }
456 properties.putAll( sanitizedEntries );
457 return properties;
458 }
459
460
461 protected static String sanitize( Object value )
462 {
463 if ( value instanceof String )
464 {
465 return ( String ) value;
466 }
467 else if ( value instanceof Iterable )
468 {
469 String delim = "";
470 StringBuilder buf = new StringBuilder();
471 for ( Object i : ( Iterable<?> ) value )
472 {
473 buf.append( delim ).append( i );
474 delim = ", ";
475 }
476 return buf.toString();
477 }
478 else if ( value.getClass().isArray() )
479 {
480 String delim = "";
481 StringBuilder buf = new StringBuilder();
482 for ( int i = 0, len = Array.getLength( value ); i < len; i++ )
483 {
484 buf.append( delim ).append( Array.get( value, i ) );
485 delim = ", ";
486 }
487 return buf.toString();
488 }
489 else
490 {
491 return String.valueOf( value );
492 }
493 }
494
495
496 protected void addMavenInstructions( MavenProject currentProject, Builder builder ) throws Exception
497 {
498 if ( currentProject.getBasedir() != null )
499 {
500
501 includeMavenResources( currentProject, builder, getLog() );
502
503
504 addLocalPackages( outputDirectory, builder );
505
506
507 addMavenSourcePath( currentProject, builder, getLog() );
508 }
509
510
511 Collection embeddableArtifacts = getEmbeddableArtifacts( currentProject, builder );
512 new DependencyEmbedder( getLog(), embeddableArtifacts ).processHeaders( builder );
513
514 if ( dumpInstructions != null || getLog().isDebugEnabled() )
515 {
516 StringBuilder buf = new StringBuilder();
517 getLog().debug( "BND Instructions:" + NL + dumpInstructions( builder.getProperties(), buf ) );
518 if ( dumpInstructions != null )
519 {
520 getLog().info( "Writing BND instructions to " + dumpInstructions );
521 dumpInstructions.getParentFile().mkdirs();
522 FileUtils.fileWrite( dumpInstructions, "# BND instructions" + NL + buf );
523 }
524 }
525
526 if ( dumpClasspath != null || getLog().isDebugEnabled() )
527 {
528 StringBuilder buf = new StringBuilder();
529 getLog().debug( "BND Classpath:" + NL + dumpClasspath( builder.getClasspath(), buf ) );
530 if ( dumpClasspath != null )
531 {
532 getLog().info( "Writing BND classpath to " + dumpClasspath );
533 dumpClasspath.getParentFile().mkdirs();
534 FileUtils.fileWrite( dumpClasspath, "# BND classpath" + NL + buf );
535 }
536 }
537 }
538
539
540 protected Builder buildOSGiBundle( MavenProject currentProject, Map originalInstructions, Properties properties,
541 Jar[] classpath ) throws Exception
542 {
543 Builder builder = getOSGiBuilder( currentProject, originalInstructions, properties, classpath );
544
545 addMavenInstructions( currentProject, builder );
546
547 builder.build();
548
549 mergeMavenManifest( currentProject, builder );
550
551 return builder;
552 }
553
554
555 protected static StringBuilder dumpInstructions( Properties properties, StringBuilder buf )
556 {
557 try
558 {
559 buf.append( "#-----------------------------------------------------------------------" + NL );
560 Properties stringProperties = new Properties();
561 for ( Enumeration e = properties.propertyNames(); e.hasMoreElements(); )
562 {
563
564 String key = ( String ) e.nextElement();
565 String value = properties.getProperty( key );
566 if ( value != null )
567 {
568 stringProperties.setProperty( key, value );
569 }
570 }
571 ByteArrayOutputStream out = new ByteArrayOutputStream();
572 stringProperties.store( out, null );
573 buf.append( out.toString( "8859_1" ) );
574 buf.append( "#-----------------------------------------------------------------------" + NL );
575 }
576 catch ( Throwable e )
577 {
578
579 }
580 return buf;
581 }
582
583
584 protected static StringBuilder dumpClasspath( List classpath, StringBuilder buf )
585 {
586 try
587 {
588 buf.append( "#-----------------------------------------------------------------------" + NL );
589 buf.append( "-classpath:\\" + NL );
590 for ( Iterator i = classpath.iterator(); i.hasNext(); )
591 {
592 File path = ( ( Jar ) i.next() ).getSource();
593 if ( path != null )
594 {
595 buf.append( ' ' + path.toString() + ( i.hasNext() ? ",\\" : "" ) + NL );
596 }
597 }
598 buf.append( "#-----------------------------------------------------------------------" + NL );
599 }
600 catch ( Throwable e )
601 {
602
603 }
604 return buf;
605 }
606
607
608 protected static StringBuilder dumpManifest( Manifest manifest, StringBuilder buf )
609 {
610 try
611 {
612 buf.append( "#-----------------------------------------------------------------------" + NL );
613 ByteArrayOutputStream out = new ByteArrayOutputStream();
614 Jar.writeManifest( manifest, out );
615 buf.append( out.toString( "UTF8" ) );
616 buf.append( "#-----------------------------------------------------------------------" + NL );
617 }
618 catch ( Throwable e )
619 {
620
621 }
622 return buf;
623 }
624
625
626 protected static void includeMavenResources( MavenProject currentProject, Analyzer analyzer, Log log )
627 {
628
629 final String mavenResourcePaths = getMavenResourcePaths( currentProject );
630 final String includeResource = ( String ) analyzer.getProperty( Analyzer.INCLUDE_RESOURCE );
631 if ( includeResource != null )
632 {
633 if ( includeResource.indexOf( MAVEN_RESOURCES ) >= 0 )
634 {
635
636
637 if ( mavenResourcePaths.length() == 0 )
638 {
639 String cleanedResource = removeTagFromInstruction( includeResource, MAVEN_RESOURCES );
640 if ( cleanedResource.length() > 0 )
641 {
642 analyzer.setProperty( Analyzer.INCLUDE_RESOURCE, cleanedResource );
643 }
644 else
645 {
646 analyzer.unsetProperty( Analyzer.INCLUDE_RESOURCE );
647 }
648 }
649 else
650 {
651 String combinedResource = StringUtils
652 .replace( includeResource, MAVEN_RESOURCES, mavenResourcePaths );
653 analyzer.setProperty( Analyzer.INCLUDE_RESOURCE, combinedResource );
654 }
655 }
656 else if ( mavenResourcePaths.length() > 0 )
657 {
658 log.warn( Analyzer.INCLUDE_RESOURCE + ": overriding " + mavenResourcePaths + " with " + includeResource
659 + " (add " + MAVEN_RESOURCES + " if you want to include the maven resources)" );
660 }
661 }
662 else if ( mavenResourcePaths.length() > 0 )
663 {
664 analyzer.setProperty( Analyzer.INCLUDE_RESOURCE, mavenResourcePaths );
665 }
666 }
667
668
669 protected void mergeMavenManifest( MavenProject currentProject, Builder builder ) throws Exception
670 {
671 Jar jar = builder.getJar();
672
673 if ( getLog().isDebugEnabled() )
674 {
675 getLog().debug( "BND Manifest:" + NL + dumpManifest( jar.getManifest(), new StringBuilder() ) );
676 }
677
678 boolean addMavenDescriptor = currentProject.getBasedir() != null;
679
680 try
681 {
682
683
684
685 MavenArchiveConfiguration archiveConfig = JarPluginConfiguration.getArchiveConfiguration( currentProject );
686 String mavenManifestText = new MavenArchiver().getManifest( currentProject, archiveConfig ).toString();
687 addMavenDescriptor = addMavenDescriptor && archiveConfig.isAddMavenDescriptor();
688
689 Manifest mavenManifest = new Manifest();
690
691
692 File externalManifestFile = archiveConfig.getManifestFile();
693 if ( null != externalManifestFile && externalManifestFile.exists()
694 && !externalManifestFile.equals( new File( manifestLocation, "MANIFEST.MF" ) ) )
695 {
696 InputStream mis = new FileInputStream( externalManifestFile );
697 mavenManifest.read( mis );
698 mis.close();
699 }
700
701
702 mavenManifest.read( new ByteArrayInputStream( mavenManifestText.getBytes( "UTF8" ) ) );
703
704 if ( !archiveConfig.isManifestSectionsEmpty() )
705 {
706
707
708
709 List sections = archiveConfig.getManifestSections();
710 for ( Iterator i = sections.iterator(); i.hasNext(); )
711 {
712 ManifestSection section = ( ManifestSection ) i.next();
713 Attributes attributes = new Attributes();
714
715 if ( !section.isManifestEntriesEmpty() )
716 {
717 Map entries = section.getManifestEntries();
718 for ( Iterator j = entries.entrySet().iterator(); j.hasNext(); )
719 {
720 Map.Entry entry = ( Map.Entry ) j.next();
721 attributes.putValue( ( String ) entry.getKey(), ( String ) entry.getValue() );
722 }
723 }
724
725 mavenManifest.getEntries().put( section.getName(), attributes );
726 }
727 }
728
729 Attributes mainMavenAttributes = mavenManifest.getMainAttributes();
730 mainMavenAttributes.putValue( "Created-By", "Apache Maven Bundle Plugin" );
731
732 String[] removeHeaders = builder.getProperty( Constants.REMOVEHEADERS, "" ).split( "," );
733
734
735 for ( int i = 0; i < removeHeaders.length; i++ )
736 {
737 for ( Iterator j = mainMavenAttributes.keySet().iterator(); j.hasNext(); )
738 {
739 if ( j.next().toString().matches( removeHeaders[i].trim() ) )
740 {
741 j.remove();
742 }
743 }
744 }
745
746
747
748
749 Manifest bundleManifest = jar.getManifest();
750 bundleManifest.getMainAttributes().putAll( mainMavenAttributes );
751 bundleManifest.getEntries().putAll( mavenManifest.getEntries() );
752
753
754
755 String importPackages = bundleManifest.getMainAttributes().getValue( "Import-Package" );
756 if ( importPackages != null )
757 {
758 Set optionalPackages = getOptionalPackages( currentProject );
759
760 Map<String, Map<String, String>> values = new Analyzer().parseHeader( importPackages );
761 for ( Map.Entry<String, Map<String, String>> entry : values.entrySet() )
762 {
763 String pkg = entry.getKey();
764 Map<String, String> options = entry.getValue();
765 if ( !options.containsKey( "resolution:" ) && optionalPackages.contains( pkg ) )
766 {
767 options.put( "resolution:", "optional" );
768 }
769 }
770 String result = Processor.printClauses( values );
771 bundleManifest.getMainAttributes().putValue( "Import-Package", result );
772 }
773
774 jar.setManifest( bundleManifest );
775 }
776 catch ( Exception e )
777 {
778 getLog().warn( "Unable to merge Maven manifest: " + e.getLocalizedMessage() );
779 }
780
781 if ( addMavenDescriptor )
782 {
783 doMavenMetadata( currentProject, jar );
784 }
785
786 if ( getLog().isDebugEnabled() )
787 {
788 getLog().debug( "Final Manifest:" + NL + dumpManifest( jar.getManifest(), new StringBuilder() ) );
789 }
790
791 builder.setJar( jar );
792 }
793
794
795 protected Set getOptionalPackages( MavenProject currentProject ) throws IOException, MojoExecutionException
796 {
797 ArrayList inscope = new ArrayList();
798 final Collection artifacts = getSelectedDependencies( currentProject.getArtifacts() );
799 for ( Iterator it = artifacts.iterator(); it.hasNext(); )
800 {
801 Artifact artifact = ( Artifact ) it.next();
802 if ( artifact.getArtifactHandler().isAddedToClasspath() )
803 {
804 if ( !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
805 {
806 inscope.add( artifact );
807 }
808 }
809 }
810
811 HashSet optionalArtifactIds = new HashSet();
812 for ( Iterator it = inscope.iterator(); it.hasNext(); )
813 {
814 Artifact artifact = ( Artifact ) it.next();
815 if ( artifact.isOptional() )
816 {
817 String id = artifact.toString();
818 if ( artifact.getScope() != null )
819 {
820
821 id = id.replaceFirst( ":[^:]*$", "" );
822 }
823 optionalArtifactIds.add( id );
824 }
825
826 }
827
828 HashSet required = new HashSet();
829 HashSet optional = new HashSet();
830 for ( Iterator it = inscope.iterator(); it.hasNext(); )
831 {
832 Artifact artifact = ( Artifact ) it.next();
833 File file = getFile( artifact );
834 if ( file == null )
835 {
836 continue;
837 }
838
839 Jar jar = new Jar( artifact.getArtifactId(), file );
840 if ( isTransitivelyOptional( optionalArtifactIds, artifact ) )
841 {
842 optional.addAll( jar.getPackages() );
843 }
844 else
845 {
846 required.addAll( jar.getPackages() );
847 }
848 jar.close();
849 }
850
851 optional.removeAll( required );
852 return optional;
853 }
854
855
856
857
858
859
860
861
862 protected boolean isTransitivelyOptional( HashSet optionalArtifactIds, Artifact artifact )
863 {
864 List trail = artifact.getDependencyTrail();
865 for ( Iterator iterator = trail.iterator(); iterator.hasNext(); )
866 {
867 String next = ( String ) iterator.next();
868 if ( optionalArtifactIds.contains( next ) )
869 {
870 return true;
871 }
872 }
873 return false;
874 }
875
876
877 private void unpackBundle( File jarFile )
878 {
879 File outputDir = getOutputDirectory();
880 if ( null == outputDir )
881 {
882 outputDir = new File( getBuildDirectory(), "classes" );
883 }
884
885 try
886 {
887
888
889
890
891 if ( !outputDir.exists() )
892 {
893 outputDir.mkdirs();
894 }
895
896 UnArchiver unArchiver = m_archiverManager.getUnArchiver( "jar" );
897 unArchiver.setDestDirectory( outputDir );
898 unArchiver.setSourceFile( jarFile );
899 unArchiver.extract();
900 }
901 catch ( Exception e )
902 {
903 getLog().error( "Problem unpacking " + jarFile + " to " + outputDir, e );
904 }
905 }
906
907
908 protected static String removeTagFromInstruction( String instruction, String tag )
909 {
910 StringBuffer buf = new StringBuffer();
911
912 String[] clauses = instruction.split( "," );
913 for ( int i = 0; i < clauses.length; i++ )
914 {
915 String clause = clauses[i].trim();
916 if ( !tag.equals( clause ) )
917 {
918 if ( buf.length() > 0 )
919 {
920 buf.append( ',' );
921 }
922 buf.append( clause );
923 }
924 }
925
926 return buf.toString();
927 }
928
929
930 private static Map getProperties( Model projectModel, String prefix )
931 {
932 Map properties = new LinkedHashMap();
933 Method methods[] = Model.class.getDeclaredMethods();
934 for ( int i = 0; i < methods.length; i++ )
935 {
936 String name = methods[i].getName();
937 if ( name.startsWith( "get" ) )
938 {
939 try
940 {
941 Object v = methods[i].invoke( projectModel, null );
942 if ( v != null )
943 {
944 name = prefix + Character.toLowerCase( name.charAt( 3 ) ) + name.substring( 4 );
945 if ( v.getClass().isArray() )
946 properties.put( name, Arrays.asList( ( Object[] ) v ).toString() );
947 else
948 properties.put( name, v );
949
950 }
951 }
952 catch ( Exception e )
953 {
954
955 }
956 }
957 }
958 return properties;
959 }
960
961
962 private static StringBuffer printLicenses( List licenses )
963 {
964 if ( licenses == null || licenses.size() == 0 )
965 return null;
966 StringBuffer sb = new StringBuffer();
967 String del = "";
968 for ( Iterator i = licenses.iterator(); i.hasNext(); )
969 {
970 License l = ( License ) i.next();
971 String url = l.getUrl();
972 if ( url == null )
973 continue;
974 sb.append( del );
975 sb.append( url );
976 del = ", ";
977 }
978 if ( sb.length() == 0 )
979 return null;
980 return sb;
981 }
982
983
984
985
986
987
988 private void doMavenMetadata( MavenProject currentProject, Jar jar ) throws IOException
989 {
990 String path = "META-INF/maven/" + currentProject.getGroupId() + "/" + currentProject.getArtifactId();
991 File pomFile = new File( currentProject.getBasedir(), "pom.xml" );
992 jar.putResource( path + "/pom.xml", new FileResource( pomFile ) );
993
994 Properties p = new Properties();
995 p.put( "version", currentProject.getVersion() );
996 p.put( "groupId", currentProject.getGroupId() );
997 p.put( "artifactId", currentProject.getArtifactId() );
998 ByteArrayOutputStream out = new ByteArrayOutputStream();
999 p.store( out, "Generated by org.apache.felix.bundleplugin" );
1000 jar.putResource( path + "/pom.properties", new EmbeddedResource( out.toByteArray(), System.currentTimeMillis() ) );
1001 }
1002
1003
1004 protected Jar[] getClasspath( MavenProject currentProject ) throws IOException, MojoExecutionException
1005 {
1006 List list = new ArrayList();
1007
1008 if ( getOutputDirectory() != null && getOutputDirectory().exists() )
1009 {
1010 list.add( new Jar( ".", getOutputDirectory() ) );
1011 }
1012
1013 final Collection artifacts = getSelectedDependencies( currentProject.getArtifacts() );
1014 for ( Iterator it = artifacts.iterator(); it.hasNext(); )
1015 {
1016 Artifact artifact = ( Artifact ) it.next();
1017 if ( artifact.getArtifactHandler().isAddedToClasspath() )
1018 {
1019 if ( !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
1020 {
1021 File file = getFile( artifact );
1022 if ( file == null )
1023 {
1024 getLog().warn(
1025 "File is not available for artifact " + artifact + " in project "
1026 + currentProject.getArtifact() );
1027 continue;
1028 }
1029 Jar jar = new Jar( artifact.getArtifactId(), file );
1030 list.add( jar );
1031 }
1032 }
1033 }
1034 Jar[] cp = new Jar[list.size()];
1035 list.toArray( cp );
1036 return cp;
1037 }
1038
1039
1040 private Collection getSelectedDependencies( Collection artifacts ) throws MojoExecutionException
1041 {
1042 if ( null == excludeDependencies || excludeDependencies.length() == 0 )
1043 {
1044 return artifacts;
1045 }
1046 else if ( "true".equalsIgnoreCase( excludeDependencies ) )
1047 {
1048 return Collections.EMPTY_LIST;
1049 }
1050
1051 Collection selectedDependencies = new LinkedHashSet( artifacts );
1052 DependencyExcluder excluder = new DependencyExcluder( artifacts );
1053 excluder.processHeaders( excludeDependencies );
1054 selectedDependencies.removeAll( excluder.getExcludedArtifacts() );
1055
1056 return selectedDependencies;
1057 }
1058
1059
1060
1061
1062
1063
1064
1065 protected File getFile( Artifact artifact )
1066 {
1067 return artifact.getFile();
1068 }
1069
1070
1071 private static void header( Properties properties, String key, Object value )
1072 {
1073 if ( value == null )
1074 return;
1075
1076 if ( value instanceof Collection && ( ( Collection ) value ).isEmpty() )
1077 return;
1078
1079 properties.put( key, value.toString().replaceAll( "[\r\n]", "" ) );
1080 }
1081
1082
1083
1084
1085
1086
1087
1088
1089 protected String convertVersionToOsgi( String version )
1090 {
1091 return getMaven2OsgiConverter().getVersion( version );
1092 }
1093
1094
1095
1096
1097
1098 protected String getBundleName( MavenProject currentProject )
1099 {
1100 String extension;
1101 try
1102 {
1103 extension = currentProject.getArtifact().getArtifactHandler().getExtension();
1104 }
1105 catch ( Throwable e )
1106 {
1107 extension = currentProject.getArtifact().getType();
1108 }
1109 if ( StringUtils.isEmpty( extension ) || "bundle".equals( extension ) || "pom".equals( extension ) )
1110 {
1111 extension = "jar";
1112 }
1113 String finalName = currentProject.getBuild().getFinalName();
1114 if ( null != classifier && classifier.trim().length() > 0 )
1115 {
1116 return finalName + '-' + classifier + '.' + extension;
1117 }
1118 return finalName + '.' + extension;
1119 }
1120
1121
1122 protected String getBuildDirectory()
1123 {
1124 return buildDirectory;
1125 }
1126
1127
1128 protected void setBuildDirectory( String _buildirectory )
1129 {
1130 buildDirectory = _buildirectory;
1131 }
1132
1133
1134 protected Properties getDefaultProperties( MavenProject currentProject )
1135 {
1136 Properties properties = new Properties();
1137
1138 String bsn;
1139 try
1140 {
1141 bsn = getMaven2OsgiConverter().getBundleSymbolicName( currentProject.getArtifact() );
1142 }
1143 catch ( Exception e )
1144 {
1145 bsn = currentProject.getGroupId() + "." + currentProject.getArtifactId();
1146 }
1147
1148
1149 properties.put( MAVEN_SYMBOLICNAME, bsn );
1150 properties.put( Analyzer.BUNDLE_SYMBOLICNAME, bsn );
1151 properties.put( Analyzer.IMPORT_PACKAGE, "*" );
1152 properties.put( Analyzer.BUNDLE_VERSION, getMaven2OsgiConverter().getVersion( currentProject.getVersion() ) );
1153
1154
1155 properties.put( Constants.REMOVEHEADERS, Analyzer.INCLUDE_RESOURCE + ',' + Analyzer.PRIVATE_PACKAGE );
1156
1157 header( properties, Analyzer.BUNDLE_DESCRIPTION, currentProject.getDescription() );
1158 StringBuffer licenseText = printLicenses( currentProject.getLicenses() );
1159 if ( licenseText != null )
1160 {
1161 header( properties, Analyzer.BUNDLE_LICENSE, licenseText );
1162 }
1163 header( properties, Analyzer.BUNDLE_NAME, currentProject.getName() );
1164
1165 if ( currentProject.getOrganization() != null )
1166 {
1167 if ( currentProject.getOrganization().getName() != null )
1168 {
1169 String organizationName = currentProject.getOrganization().getName();
1170 header( properties, Analyzer.BUNDLE_VENDOR, organizationName );
1171 properties.put( "project.organization.name", organizationName );
1172 properties.put( "pom.organization.name", organizationName );
1173 }
1174 if ( currentProject.getOrganization().getUrl() != null )
1175 {
1176 String organizationUrl = currentProject.getOrganization().getUrl();
1177 header( properties, Analyzer.BUNDLE_DOCURL, organizationUrl );
1178 properties.put( "project.organization.url", organizationUrl );
1179 properties.put( "pom.organization.url", organizationUrl );
1180 }
1181 }
1182
1183 properties.putAll( currentProject.getProperties() );
1184 properties.putAll( currentProject.getModel().getProperties() );
1185 if ( m_mavenSession != null )
1186 {
1187 try
1188 {
1189
1190 Properties sessionProperties = m_mavenSession.getExecutionProperties();
1191 for ( Enumeration e = sessionProperties.propertyNames(); e.hasMoreElements(); )
1192 {
1193 String key = ( String ) e.nextElement();
1194 if ( key.length() > 0 && !Character.isUpperCase( key.charAt( 0 ) ) )
1195 {
1196 properties.put( key, sessionProperties.getProperty( key ) );
1197 }
1198 }
1199 }
1200 catch ( Exception e )
1201 {
1202 getLog().warn( "Problem with Maven session properties: " + e.getLocalizedMessage() );
1203 }
1204 }
1205
1206 properties.putAll( getProperties( currentProject.getModel(), "project.build." ) );
1207 properties.putAll( getProperties( currentProject.getModel(), "pom." ) );
1208 properties.putAll( getProperties( currentProject.getModel(), "project." ) );
1209
1210 properties.put( "project.baseDir", getBase( currentProject ) );
1211 properties.put( "project.build.directory", getBuildDirectory() );
1212 properties.put( "project.build.outputdirectory", getOutputDirectory() );
1213
1214 properties.put( "classifier", classifier == null ? "" : classifier );
1215
1216
1217 header( properties, Analyzer.PLUGIN, BlueprintPlugin.class.getName() + "," + SpringXMLType.class.getName() );
1218
1219 return properties;
1220 }
1221
1222
1223 protected static File getBase( MavenProject currentProject )
1224 {
1225 return currentProject.getBasedir() != null ? currentProject.getBasedir() : new File( "" );
1226 }
1227
1228
1229 protected File getOutputDirectory()
1230 {
1231 return outputDirectory;
1232 }
1233
1234
1235 protected void setOutputDirectory( File _outputDirectory )
1236 {
1237 outputDirectory = _outputDirectory;
1238 }
1239
1240
1241 private static void addLocalPackages( File outputDirectory, Analyzer analyzer )
1242 {
1243 Collection packages = new TreeSet();
1244
1245 if ( outputDirectory != null && outputDirectory.isDirectory() )
1246 {
1247
1248 DirectoryScanner scanner = new DirectoryScanner();
1249 scanner.setBasedir( outputDirectory );
1250 scanner.setIncludes( new String[]
1251 { "**/*.class" } );
1252
1253 scanner.addDefaultExcludes();
1254 scanner.scan();
1255
1256 String[] paths = scanner.getIncludedFiles();
1257 for ( int i = 0; i < paths.length; i++ )
1258 {
1259 packages.add( getPackageName( paths[i] ) );
1260 }
1261 }
1262
1263 StringBuffer exportedPkgs = new StringBuffer();
1264 StringBuffer privatePkgs = new StringBuffer();
1265
1266 boolean noprivatePackages = "!*".equals( analyzer.getProperty( Analyzer.PRIVATE_PACKAGE ) );
1267
1268 for ( Iterator i = packages.iterator(); i.hasNext(); )
1269 {
1270 String pkg = ( String ) i.next();
1271
1272
1273 if ( privatePkgs.length() > 0 )
1274 {
1275 privatePkgs.append( ';' );
1276 }
1277 privatePkgs.append( pkg );
1278
1279
1280 if ( noprivatePackages || !( ".".equals( pkg ) || pkg.contains( ".internal" ) || pkg.contains( ".impl" ) ) )
1281 {
1282 if ( exportedPkgs.length() > 0 )
1283 {
1284 exportedPkgs.append( ';' );
1285 }
1286 exportedPkgs.append( pkg );
1287 }
1288 }
1289
1290 if ( analyzer.getProperty( Analyzer.EXPORT_PACKAGE ) == null )
1291 {
1292 if ( analyzer.getProperty( Analyzer.EXPORT_CONTENTS ) == null )
1293 {
1294
1295 analyzer.setProperty( Analyzer.EXPORT_PACKAGE, exportedPkgs + ";-split-package:=merge-first" );
1296 }
1297 else
1298 {
1299
1300 analyzer.setProperty( Analyzer.EXPORT_PACKAGE, "" );
1301 }
1302 }
1303 else
1304 {
1305 String exported = analyzer.getProperty( Analyzer.EXPORT_PACKAGE );
1306 if ( exported.indexOf( LOCAL_PACKAGES ) >= 0 )
1307 {
1308 String newExported = StringUtils.replace( exported, LOCAL_PACKAGES, exportedPkgs.toString() );
1309 analyzer.setProperty( Analyzer.EXPORT_PACKAGE, newExported );
1310
1311 }
1312 }
1313
1314 String internal = analyzer.getProperty( Analyzer.PRIVATE_PACKAGE );
1315 if ( internal == null )
1316 {
1317 if ( privatePkgs.length() > 0 )
1318 {
1319 analyzer.setProperty( Analyzer.PRIVATE_PACKAGE, privatePkgs + ";-split-package:=merge-first" );
1320 }
1321 else
1322 {
1323
1324 analyzer.setProperty( Analyzer.PRIVATE_PACKAGE, "!*" );
1325 }
1326 }
1327 else if ( internal.indexOf( LOCAL_PACKAGES ) >= 0 )
1328 {
1329 String newInternal = StringUtils.replace( internal, LOCAL_PACKAGES, privatePkgs.toString() );
1330 analyzer.setProperty( Analyzer.PRIVATE_PACKAGE, newInternal );
1331 }
1332 }
1333
1334
1335 private static String getPackageName( String filename )
1336 {
1337 int n = filename.lastIndexOf( File.separatorChar );
1338 return n < 0 ? "." : filename.substring( 0, n ).replace( File.separatorChar, '.' );
1339 }
1340
1341
1342 private static List getMavenResources( MavenProject currentProject )
1343 {
1344 List resources = new ArrayList( currentProject.getResources() );
1345
1346 if ( currentProject.getCompileSourceRoots() != null )
1347 {
1348
1349 List packageInfoIncludes = Collections.singletonList( "**/packageinfo" );
1350 for ( Iterator i = currentProject.getCompileSourceRoots().iterator(); i.hasNext(); )
1351 {
1352 String sourceRoot = ( String ) i.next();
1353 Resource packageInfoResource = new Resource();
1354 packageInfoResource.setDirectory( sourceRoot );
1355 packageInfoResource.setIncludes( packageInfoIncludes );
1356 resources.add( packageInfoResource );
1357 }
1358 }
1359
1360 return resources;
1361 }
1362
1363
1364 protected static String getMavenResourcePaths( MavenProject currentProject )
1365 {
1366 final String basePath = currentProject.getBasedir().getAbsolutePath();
1367
1368 Set pathSet = new LinkedHashSet();
1369 for ( Iterator i = getMavenResources( currentProject ).iterator(); i.hasNext(); )
1370 {
1371 Resource resource = ( Resource ) i.next();
1372
1373 final String sourcePath = resource.getDirectory();
1374 final String targetPath = resource.getTargetPath();
1375
1376
1377 if ( new File( sourcePath ).exists() && ( ( targetPath == null ) || ( targetPath.indexOf( ".." ) < 0 ) ) )
1378 {
1379 DirectoryScanner scanner = new DirectoryScanner();
1380
1381 scanner.setBasedir( sourcePath );
1382 if ( resource.getIncludes() != null && !resource.getIncludes().isEmpty() )
1383 {
1384 scanner.setIncludes( ( String[] ) resource.getIncludes().toArray( EMPTY_STRING_ARRAY ) );
1385 }
1386 else
1387 {
1388 scanner.setIncludes( DEFAULT_INCLUDES );
1389 }
1390
1391 if ( resource.getExcludes() != null && !resource.getExcludes().isEmpty() )
1392 {
1393 scanner.setExcludes( ( String[] ) resource.getExcludes().toArray( EMPTY_STRING_ARRAY ) );
1394 }
1395
1396 scanner.addDefaultExcludes();
1397 scanner.scan();
1398
1399 List includedFiles = Arrays.asList( scanner.getIncludedFiles() );
1400
1401 for ( Iterator j = includedFiles.iterator(); j.hasNext(); )
1402 {
1403 String name = ( String ) j.next();
1404 String path = sourcePath + '/' + name;
1405
1406
1407 if ( path.startsWith( basePath ) )
1408 {
1409 if ( path.length() == basePath.length() )
1410 {
1411 path = ".";
1412 }
1413 else
1414 {
1415 path = path.substring( basePath.length() + 1 );
1416 }
1417 }
1418
1419
1420
1421 if ( File.separatorChar != '/' )
1422 {
1423 name = name.replace( File.separatorChar, '/' );
1424 path = path.replace( File.separatorChar, '/' );
1425 }
1426
1427
1428 path = name + '=' + path;
1429 if ( targetPath != null )
1430 {
1431 path = targetPath + '/' + path;
1432 }
1433
1434
1435 if ( resource.isFiltering() )
1436 {
1437 path = '{' + path + '}';
1438 }
1439
1440 pathSet.add( path );
1441 }
1442 }
1443 }
1444
1445 StringBuffer resourcePaths = new StringBuffer();
1446 for ( Iterator i = pathSet.iterator(); i.hasNext(); )
1447 {
1448 resourcePaths.append( i.next() );
1449 if ( i.hasNext() )
1450 {
1451 resourcePaths.append( ',' );
1452 }
1453 }
1454
1455 return resourcePaths.toString();
1456 }
1457
1458
1459 protected Collection getEmbeddableArtifacts( MavenProject currentProject, Analyzer analyzer )
1460 throws MojoExecutionException
1461 {
1462 final Collection artifacts;
1463
1464 String embedTransitive = analyzer.getProperty( DependencyEmbedder.EMBED_TRANSITIVE );
1465 if ( Boolean.valueOf( embedTransitive ).booleanValue() )
1466 {
1467
1468 artifacts = currentProject.getArtifacts();
1469 }
1470 else
1471 {
1472
1473 artifacts = currentProject.getDependencyArtifacts();
1474 }
1475
1476 return getSelectedDependencies( artifacts );
1477 }
1478
1479
1480 protected static void addMavenSourcePath( MavenProject currentProject, Analyzer analyzer, Log log )
1481 {
1482
1483 StringBuilder mavenSourcePaths = new StringBuilder();
1484 if ( currentProject.getCompileSourceRoots() != null )
1485 {
1486 for ( Iterator i = currentProject.getCompileSourceRoots().iterator(); i.hasNext(); )
1487 {
1488 if ( mavenSourcePaths.length() > 0 )
1489 {
1490 mavenSourcePaths.append( ',' );
1491 }
1492 mavenSourcePaths.append( ( String ) i.next() );
1493 }
1494 }
1495 final String sourcePath = ( String ) analyzer.getProperty( Analyzer.SOURCEPATH );
1496 if ( sourcePath != null )
1497 {
1498 if ( sourcePath.indexOf( MAVEN_SOURCES ) >= 0 )
1499 {
1500
1501
1502 if ( mavenSourcePaths.length() == 0 )
1503 {
1504 String cleanedSource = removeTagFromInstruction( sourcePath, MAVEN_SOURCES );
1505 if ( cleanedSource.length() > 0 )
1506 {
1507 analyzer.setProperty( Analyzer.SOURCEPATH, cleanedSource );
1508 }
1509 else
1510 {
1511 analyzer.unsetProperty( Analyzer.SOURCEPATH );
1512 }
1513 }
1514 else
1515 {
1516 String combinedSource = StringUtils
1517 .replace( sourcePath, MAVEN_SOURCES, mavenSourcePaths.toString() );
1518 analyzer.setProperty( Analyzer.SOURCEPATH, combinedSource );
1519 }
1520 }
1521 else if ( mavenSourcePaths.length() > 0 )
1522 {
1523 log.warn( Analyzer.SOURCEPATH + ": overriding " + mavenSourcePaths + " with " + sourcePath + " (add "
1524 + MAVEN_SOURCES + " if you want to include the maven sources)" );
1525 }
1526 }
1527 else if ( mavenSourcePaths.length() > 0 )
1528 {
1529 analyzer.setProperty( Analyzer.SOURCEPATH, mavenSourcePaths.toString() );
1530 }
1531 }
1532 }