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.File;
23 import java.io.FileInputStream;
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.OutputStream;
28 import java.util.Iterator;
29 import java.util.LinkedHashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Map.Entry;
33 import java.util.Properties;
34 import java.util.jar.Manifest;
35
36 import org.apache.maven.plugin.MojoExecutionException;
37 import org.apache.maven.plugin.MojoFailureException;
38 import org.apache.maven.plugin.logging.Log;
39 import org.apache.maven.plugins.annotations.Component;
40 import org.apache.maven.plugins.annotations.LifecyclePhase;
41 import org.apache.maven.plugins.annotations.Mojo;
42 import org.apache.maven.plugins.annotations.Parameter;
43 import org.apache.maven.plugins.annotations.ResolutionScope;
44 import org.apache.maven.project.MavenProject;
45 import org.apache.maven.shared.dependency.graph.DependencyNode;
46 import org.codehaus.plexus.util.Scanner;
47 import org.osgi.service.metatype.MetaTypeService;
48 import org.sonatype.plexus.build.incremental.BuildContext;
49
50 import aQute.bnd.osgi.Analyzer;
51 import aQute.bnd.osgi.Builder;
52 import aQute.bnd.osgi.Instructions;
53 import aQute.bnd.osgi.Jar;
54 import aQute.bnd.osgi.Resource;
55 import aQute.lib.collections.ExtList;
56
57
58
59
60
61 @Mojo( name = "manifest", requiresDependencyResolution = ResolutionScope.TEST,
62 threadSafe = true,
63 defaultPhase = LifecyclePhase.PROCESS_CLASSES)
64 public class ManifestPlugin extends BundlePlugin
65 {
66
67
68
69 @Parameter( property = "rebuildBundle" )
70 protected boolean rebuildBundle;
71
72
73
74
75
76
77 @Parameter( property = "supportIncrementalBuild" )
78 private boolean supportIncrementalBuild;
79
80 @Component
81 private BuildContext buildContext;
82
83 @Override
84 protected void execute( MavenProject project, DependencyNode dependencyGraph, Map<String, String> instructions, Properties properties, Jar[] classpath )
85 throws MojoExecutionException
86 {
87
88
89
90 if (buildContext.isIncremental() && !(supportIncrementalBuild && anyJavaSourceFileTouchedSinceLastBuild())) {
91 getLog().debug("Skipping manifest generation because no java source file was added, updated or removed since last build.");
92 return;
93 }
94
95 Analyzer analyzer;
96 try
97 {
98 analyzer = getAnalyzer(project, dependencyGraph, instructions, properties, classpath);
99 }
100 catch ( FileNotFoundException e )
101 {
102 throw new MojoExecutionException( "Cannot find " + e.getMessage()
103 + " (manifest goal must be run after compile phase)", e );
104 }
105 catch ( IOException e )
106 {
107 throw new MojoExecutionException( "Error trying to generate Manifest", e );
108 }
109 catch ( MojoFailureException e )
110 {
111 getLog().error( e.getLocalizedMessage() );
112 throw new MojoExecutionException( "Error(s) found in manifest configuration", e );
113 }
114 catch ( Exception e )
115 {
116 getLog().error( "An internal error occurred", e );
117 throw new MojoExecutionException( "Internal error in maven-bundle-plugin", e );
118 }
119
120 File outputFile = new File( manifestLocation, "MANIFEST.MF" );
121
122 try
123 {
124 writeManifest( analyzer, outputFile, niceManifest, exportScr, scrLocation, buildContext, getLog() );
125 }
126 catch ( Exception e )
127 {
128 throw new MojoExecutionException( "Error trying to write Manifest to file " + outputFile, e );
129 }
130 finally
131 {
132 try
133 {
134 analyzer.close();
135 }
136 catch ( IOException e )
137 {
138 throw new MojoExecutionException( "Error trying to write Manifest to file " + outputFile, e );
139 }
140 }
141 }
142
143
144
145
146 private boolean anyJavaSourceFileTouchedSinceLastBuild() {
147 @SuppressWarnings("unchecked")
148 List<String> sourceDirectories = project.getCompileSourceRoots();
149 for (String sourceDirectory : sourceDirectories) {
150 File directory = new File(sourceDirectory);
151 Scanner scanner = buildContext.newScanner(directory);
152 Scanner deleteScanner = buildContext.newDeleteScanner(directory);
153 if (containsJavaFile(scanner) || containsJavaFile(deleteScanner)) {
154 return true;
155 }
156 }
157 return false;
158 }
159 private boolean containsJavaFile(Scanner scanner) {
160 String[] includes = new String[] { "**/*.java" };
161 scanner.setIncludes(includes);
162 scanner.scan();
163 return scanner.getIncludedFiles().length > 0;
164 }
165
166 public Manifest getManifest( MavenProject project, DependencyNode dependencyGraph, Jar[] classpath ) throws IOException, MojoFailureException,
167 MojoExecutionException, Exception
168 {
169 return getManifest( project, dependencyGraph, new LinkedHashMap<String, String>(), new Properties(), classpath, buildContext );
170 }
171
172
173 public Manifest getManifest( MavenProject project, DependencyNode dependencyGraph, Map<String, String> instructions, Properties properties, Jar[] classpath,
174 BuildContext buildContext) throws IOException, MojoFailureException, MojoExecutionException, Exception
175 {
176 Analyzer analyzer = getAnalyzer(project, dependencyGraph, instructions, properties, classpath);
177
178 Jar jar = analyzer.getJar();
179 Manifest manifest = jar.getManifest();
180
181 if (exportScr)
182 {
183 exportScr(analyzer, jar, scrLocation, buildContext, getLog() );
184 }
185
186
187 analyzer.close();
188
189 return manifest;
190 }
191
192 private static void exportScr(Analyzer analyzer, Jar jar, File scrLocation, BuildContext buildContext, Log log ) throws Exception {
193 log.debug("Export SCR metadata to: " + scrLocation.getPath());
194 scrLocation.mkdirs();
195
196
197 Map<String, Resource> scrDir = jar.getDirectories().get("OSGI-INF");
198 if (scrDir != null) {
199 for (Map.Entry<String, Resource> entry : scrDir.entrySet()) {
200 String path = entry.getKey();
201 Resource resource = entry.getValue();
202 writeSCR(resource, new File(scrLocation, path), buildContext,
203 log);
204 }
205 }
206
207
208 Map<String,Resource> metatypeDir = jar.getDirectories().get(MetaTypeService.METATYPE_DOCUMENTS_LOCATION);
209 if (metatypeDir != null) {
210 for (Map.Entry<String, Resource> entry : metatypeDir.entrySet())
211 {
212 String path = entry.getKey();
213 Resource resource = entry.getValue();
214 writeSCR(resource, new File(scrLocation, path), buildContext, log);
215 }
216 }
217
218 }
219
220 private static void writeSCR(Resource resource, File destination, BuildContext buildContext, Log log ) throws Exception
221 {
222 log.debug("Write SCR file: " + destination.getPath());
223 destination.getParentFile().mkdirs();
224 OutputStream os = buildContext.newFileOutputStream(destination);
225 try
226 {
227 resource.write(os);
228 }
229 finally
230 {
231 os.close();
232 }
233 }
234
235 protected Analyzer getAnalyzer( MavenProject project, DependencyNode dependencyGraph, Jar[] classpath ) throws IOException, MojoExecutionException,
236 Exception
237 {
238 return getAnalyzer( project, dependencyGraph, new LinkedHashMap<String, String>(), new Properties(), classpath );
239 }
240
241
242 protected Analyzer getAnalyzer( MavenProject project, DependencyNode dependencyGraph, Map<String, String> instructions, Properties properties, Jar[] classpath )
243 throws IOException, MojoExecutionException, Exception
244 {
245 if ( rebuildBundle && supportedProjectTypes.contains( project.getArtifact().getType() ) )
246 {
247 return buildOSGiBundle( project, dependencyGraph, instructions, properties, classpath );
248 }
249
250 File file = getOutputDirectory();
251 if ( file == null )
252 {
253 file = project.getArtifact().getFile();
254 }
255
256 if ( !file.exists() )
257 {
258 if ( file.equals( getOutputDirectory() ) )
259 {
260 file.mkdirs();
261 }
262 else
263 {
264 throw new FileNotFoundException( file.getPath() );
265 }
266 }
267
268 Builder analyzer = getOSGiBuilder( project, instructions, properties, classpath );
269
270 analyzer.setJar( file );
271
272
273
274
275
276 boolean isOutputDirectory = file.equals( getOutputDirectory() );
277
278 if ( analyzer.getProperty( Analyzer.EXPORT_PACKAGE ) == null
279 && analyzer.getProperty( Analyzer.EXPORT_CONTENTS ) == null
280 && analyzer.getProperty( Analyzer.PRIVATE_PACKAGE ) == null && !isOutputDirectory )
281 {
282 String export = calculateExportsFromContents( analyzer.getJar() );
283 analyzer.setProperty( Analyzer.EXPORT_PACKAGE, export );
284 }
285
286 addMavenInstructions( project, dependencyGraph, analyzer );
287
288
289 if ( analyzer.getProperty( DependencyEmbedder.EMBED_DEPENDENCY ) != null && isOutputDirectory )
290 {
291 analyzer.build();
292 }
293 else
294 {
295 analyzer.mergeManifest( analyzer.getJar().getManifest() );
296 analyzer.getJar().setManifest( analyzer.calcManifest() );
297 }
298
299 mergeMavenManifest( project, dependencyGraph, analyzer );
300
301 boolean hasErrors = reportErrors( "Manifest " + project.getArtifact(), analyzer );
302 if ( hasErrors )
303 {
304 String failok = analyzer.getProperty( "-failok" );
305 if ( null == failok || "false".equalsIgnoreCase( failok ) )
306 {
307 throw new MojoFailureException( "Error(s) found in manifest configuration" );
308 }
309 }
310
311 Jar jar = analyzer.getJar();
312
313 if ( unpackBundle )
314 {
315 File outputFile = getOutputDirectory();
316 for ( Entry<String, Resource> entry : jar.getResources().entrySet() )
317 {
318 File entryFile = new File( outputFile, entry.getKey() );
319 if ( !entryFile.exists() || entry.getValue().lastModified() == 0 )
320 {
321 entryFile.getParentFile().mkdirs();
322 OutputStream os = buildContext.newFileOutputStream( entryFile );
323 entry.getValue().write( os );
324 os.close();
325 }
326 }
327 }
328
329 return analyzer;
330 }
331
332
333 public static void writeManifest( Analyzer analyzer, File outputFile, boolean niceManifest,
334 boolean exportScr, File scrLocation, BuildContext buildContext, Log log ) throws Exception
335 {
336 Properties properties = analyzer.getProperties();
337 Jar jar = analyzer.getJar();
338 Manifest manifest = jar.getManifest();
339 if ( outputFile.exists() && properties.containsKey( "Merge-Headers" ) )
340 {
341 Manifest analyzerManifest = manifest;
342 manifest = new Manifest();
343 InputStream inputStream = new FileInputStream( outputFile );
344 try
345 {
346 manifest.read( inputStream );
347 }
348 finally
349 {
350 inputStream.close();
351 }
352 Instructions instructions = new Instructions( ExtList.from( analyzer.getProperty("Merge-Headers") ) );
353 mergeManifest( instructions, manifest, analyzerManifest );
354 }
355 else
356 {
357 File parentFile = outputFile.getParentFile();
358 parentFile.mkdirs();
359 }
360 writeManifest( manifest, outputFile, niceManifest, buildContext, log );
361
362 if (exportScr)
363 {
364 exportScr(analyzer, jar, scrLocation, buildContext, log);
365 }
366 }
367
368
369 public static void writeManifest( Manifest manifest, File outputFile, boolean niceManifest,
370 BuildContext buildContext, Log log ) throws IOException
371 {
372 log.debug("Write manifest to " + outputFile.getPath());
373 outputFile.getParentFile().mkdirs();
374
375 OutputStream os = buildContext.newFileOutputStream( outputFile );
376 try
377 {
378 ManifestWriter.outputManifest( manifest, os, niceManifest );
379 }
380 finally
381 {
382 try
383 {
384 os.close();
385 }
386 catch ( IOException e )
387 {
388
389 }
390 }
391 }
392
393
394
395
396
397 public static String calculateExportsFromContents( Jar bundle )
398 {
399 String ddel = "";
400 StringBuffer sb = new StringBuffer();
401 Map<String, Map<String, Resource>> map = bundle.getDirectories();
402 for ( Iterator<Entry<String, Map<String, Resource>>> i = map.entrySet().iterator(); i.hasNext(); )
403 {
404
405
406
407 Entry<String, Map<String, Resource>> entry = i.next();
408 if ( entry.getValue() == null || entry.getValue().isEmpty() )
409 continue;
410
411 String directory = entry.getKey();
412 if ( directory.equals( "META-INF" ) || directory.startsWith( "META-INF/" ) )
413 continue;
414 if ( directory.equals( "OSGI-OPT" ) || directory.startsWith( "OSGI-OPT/" ) )
415 continue;
416 if ( directory.equals( "/" ) )
417 continue;
418
419 if ( directory.endsWith( "/" ) )
420 directory = directory.substring( 0, directory.length() - 1 );
421
422 directory = directory.replace( '/', '.' );
423 sb.append( ddel );
424 sb.append( directory );
425 ddel = ",";
426 }
427 return sb.toString();
428 }
429 }