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