diff --git a/biz.aQute.bndlib.tests/test/test/annotationheaders/SPIAnnotationsTest.java b/biz.aQute.bndlib.tests/test/test/annotationheaders/SPIAnnotationsTest.java index c072505fd5..428ac939d3 100644 --- a/biz.aQute.bndlib.tests/test/test/annotationheaders/SPIAnnotationsTest.java +++ b/biz.aQute.bndlib.tests/test/test/annotationheaders/SPIAnnotationsTest.java @@ -72,6 +72,7 @@ public void testServiceProvider_existingdescriptor() throws Exception { "META-INF/services/test.annotationheaders.spi.SPIService;literal='test.annotationheaders.spi.providerE.Provider'"); b.setProperty("Provide-Capability", "osgi.serviceloader;osgi.serviceloader='test.annotationheaders.spi.SPIService';register:='test.annotationheaders.spi.providerE.Provider'"); + b.setProperty(Constants.METAINF_SERVICES, Constants.METAINF_SERVICES_STRATEGY_ANNOTATION); b.build(); b.getJar() .getManifest() @@ -102,6 +103,46 @@ public void testServiceProvider_existingdescriptor() throws Exception { } } + @Test + public void testServiceProvider_existingdescriptorAuto() throws Exception { + try (Builder b = new Builder();) { + b.addClasspath(IO.getFile("bin_test")); + b.setPrivatePackage("test.annotationheaders.spi.providerE"); + b.setProperty("-includeresource", + "META-INF/services/test.annotationheaders.spi.SPIService;literal='test.annotationheaders.spi.providerE.Provider'"); + b.setProperty("Provide-Capability", + "osgi.serviceloader;osgi.serviceloader='test.annotationheaders.spi.SPIService';register:='test.annotationheaders.spi.providerE.Provider'"); + b.setProperty(Constants.METAINF_SERVICES, Constants.METAINF_SERVICES_STRATEGY_AUTO); + b.build(); + b.getJar() + .getManifest() + .write(System.out); + assertTrue(b.check()); + + Attributes mainAttributes = b.getJar() + .getManifest() + .getMainAttributes(); + + Header req = Header.parseHeader(mainAttributes.getValue(Constants.REQUIRE_CAPABILITY)); + assertEquals(2, req.size()); + + assertEE(req); + + Header cap = Header.parseHeader(mainAttributes.getValue(Constants.PROVIDE_CAPABILITY)); + assertEquals(3, cap.size()); + + Props p = cap.get("osgi.serviceloader"); + assertNotNull(p); + assertNotNull(p.get("osgi.serviceloader")); + assertEquals("test.annotationheaders.spi.SPIService", p.get("osgi.serviceloader")); + assertNotNull(p.get("register:")); + assertEquals("test.annotationheaders.spi.providerE.Provider", p.get("register:")); + + assertServiceMappingFile(b.getJar(), "test.annotationheaders.spi.SPIService", + "test.annotationheaders.spi.providerE.Provider"); + } + } + @Test public void testServiceProvider_warning() throws Exception { try (Builder b = new Builder();) { @@ -144,9 +185,10 @@ public void testServiceProvider_nowarning_onexisting() throws Exception { b.addClasspath(IO.getFile("bin_test")); b.setPrivatePackage("test.annotationheaders.spi.providerE"); b.setProperty("-includeresource", - "META-INF/services/test.annotationheaders.spi.SPIService;literal='some.other.Provider'"); + "META-INF/services/test.annotationheaders.spi.SPIService;literal='java.lang.String'"); b.setProperty("Provide-Capability", "osgi.serviceloader;osgi.serviceloader=\"test.annotationheaders.spi.SPIService\""); + b.setProperty(Constants.METAINF_SERVICES, Constants.METAINF_SERVICES_STRATEGY_ANNOTATION); b.build(); b.getJar() .getManifest() @@ -171,7 +213,47 @@ public void testServiceProvider_nowarning_onexisting() throws Exception { assertEquals("test.annotationheaders.spi.SPIService", p.get("osgi.serviceloader")); assertNull(p.get("register:")); - assertServiceMappingFile(b.getJar(), "test.annotationheaders.spi.SPIService", "some.other.Provider"); + assertServiceMappingFile(b.getJar(), "test.annotationheaders.spi.SPIService", "java.lang.String"); + assertServiceMappingFileNotContains(b.getJar(), "test.annotationheaders.spi.SPIService", + "another.provider.ProviderImpl"); + } + } + + @Test + public void testServiceProvider_nowarning_onexistingAuto() throws Exception { + try (Builder b = new Builder();) { + b.addClasspath(IO.getFile("bin_test")); + b.setPrivatePackage("test.annotationheaders.spi.providerE"); + b.setProperty("-includeresource", + "META-INF/services/test.annotationheaders.spi.SPIService;literal='java.lang.String'"); + b.setProperty("Provide-Capability", + "osgi.serviceloader;osgi.serviceloader=\"test.annotationheaders.spi.SPIService\""); + b.setProperty(Constants.METAINF_SERVICES, Constants.METAINF_SERVICES_STRATEGY_AUTO); + b.build(); + b.getJar() + .getManifest() + .write(System.out); + assertTrue(b.check()); + + Attributes mainAttributes = b.getJar() + .getManifest() + .getMainAttributes(); + + Header req = Header.parseHeader(mainAttributes.getValue(Constants.REQUIRE_CAPABILITY)); + assertEquals(2, req.size()); + + assertEE(req); + + Header cap = Header.parseHeader(mainAttributes.getValue(Constants.PROVIDE_CAPABILITY)); + assertEquals(3, cap.size()); + + Props p = cap.get("osgi.serviceloader"); + assertNotNull(p); + assertNotNull(p.get("osgi.serviceloader")); + assertEquals("test.annotationheaders.spi.SPIService", p.get("osgi.serviceloader")); + assertNull(p.get("register:")); + + assertServiceMappingFile(b.getJar(), "test.annotationheaders.spi.SPIService", "java.lang.String"); assertServiceMappingFileNotContains(b.getJar(), "test.annotationheaders.spi.SPIService", "another.provider.ProviderImpl"); } @@ -184,6 +266,7 @@ public void testServiceProvider_mergeDescriptor() throws Exception { b.setPrivatePackage("test.annotationheaders.spi.providerD"); b.setProperty("-includeresource", "META-INF/services/test.annotationheaders.spi.SPIService=testresources/services"); + b.setProperty(Constants.METAINF_SERVICES, Constants.METAINF_SERVICES_STRATEGY_ANNOTATION); b.build(); b.getJar() .getManifest() @@ -219,6 +302,57 @@ public void testServiceProvider_mergeDescriptor() throws Exception { } } + @Test + public void testServiceProvider_mergeDescriptorAuto() throws Exception { + try (Builder b = new Builder();) { + b.addClasspath(IO.getFile("bin_test")); + b.setPrivatePackage("test.annotationheaders.spi.providerD"); + b.setProperty("-includeresource", + "META-INF/services/test.annotationheaders.spi.SPIService=testresources/services"); + b.setProperty(Constants.METAINF_SERVICES, Constants.METAINF_SERVICES_STRATEGY_AUTO); + b.build(); + b.getJar() + .getManifest() + .write(System.out); + assertFalse(b.check()); + + Attributes mainAttributes = b.getJar() + .getManifest() + .getMainAttributes(); + + assertThat(b.getErrors()).hasSize(2); + assertThat(b.getErrors() + .get(0)).contains( + "analyzer processing annotation some.provider.Provider but the associated class is not found in the JAR"); + assertThat(b.getErrors() + .get(1)).contains( + "analyzer processing annotation another.provider.ProviderImpl but the associated class is not found in the JAR"); + + Header req = Header.parseHeader(mainAttributes.getValue(Constants.REQUIRE_CAPABILITY)); + assertEquals(2, req.size()); + + assertExtender(req, "osgi.serviceloader.registrar"); + assertEE(req); + + Header cap = Header.parseHeader(mainAttributes.getValue(Constants.PROVIDE_CAPABILITY)); + assertEquals(2, cap.size()); + + Props p = cap.get("osgi.serviceloader"); + assertNotNull(p); + assertNotNull(p.get("osgi.serviceloader")); + assertEquals("test.annotationheaders.spi.SPIService", p.get("osgi.serviceloader")); + assertNotNull(p.get("register:")); + assertThat(p.get("register:")).isIn("test.annotationheaders.spi.providerD.Provider"); + assertNotNull(p.get("foo")); + assertEquals("bar", p.get("foo")); + + assertServiceMappingFile(b.getJar(), "test.annotationheaders.spi.SPIService", + "test.annotationheaders.spi.providerD.Provider"); + assertServiceMappingFile(b.getJar(), "test.annotationheaders.spi.SPIService", + "another.provider.ProviderImpl"); + } + } + @Test public void testServiceConsumerMetaAnnotatingCustom() throws Exception { try (Builder b = new Builder();) { diff --git a/biz.aQute.bndlib.tests/test/test/annotationheaders/ServiceProviderFileTest.java b/biz.aQute.bndlib.tests/test/test/annotationheaders/ServiceProviderFileTest.java index 1d67cad542..482c25d4fb 100644 --- a/biz.aQute.bndlib.tests/test/test/annotationheaders/ServiceProviderFileTest.java +++ b/biz.aQute.bndlib.tests/test/test/annotationheaders/ServiceProviderFileTest.java @@ -1,13 +1,20 @@ package test.annotationheaders; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; import org.junit.jupiter.api.Test; +import org.osgi.resource.Requirement; import aQute.bnd.header.Parameters; import aQute.bnd.osgi.Builder; import aQute.bnd.osgi.Domain; +import aQute.bnd.osgi.resource.CapReqBuilder; import aQute.lib.io.IO; public class ServiceProviderFileTest { @@ -96,7 +103,93 @@ public void testBothMetaInfoAndAnnotationsNoParentheses() throws Exception { Parameters requireCapability = manifest.getRequireCapability(); assertThat(provideCapability.size()).isEqualTo(4); assertThat(requireCapability.size()).isEqualTo(2); + + Requirement req = CapReqBuilder.getRequirementsFrom(requireCapability) + .get(0); + assertEquals("osgi.extender", req.getNamespace()); + assertNull(req.getDirectives() + .get("resolution")); + assertEquals("(&(osgi.extender=osgi.serviceloader.registrar)(version>=1.0.0)(!(version>=2.0.0)))", + req.getDirectives() + .get("filter")); + + } + } + + @SuppressWarnings("unchecked") + @Test + public void testAutoGenerateServiceProviderAnnotation() throws Exception { + try (Builder b = new Builder();) { + b.addClasspath(IO.getFile("bin_test")); + b.setProperty("-includeresource", """ + META-INF/services/com.example.service.Type;\ + literal='\ + java.lang.String' + """); + b.setProperty("-metainf-services", "auto"); + b.build(); + assertTrue(b.check()); + Domain manifest = Domain.domain(b.getJar() + .getManifest()); + Parameters provideCapability = manifest.getProvideCapability(); + + assertThat(provideCapability.get("osgi.service")).isNotNull(); + assertThat(provideCapability.get("osgi.service")).containsEntry("objectClass", "com.example.service.Type"); + + assertThat(provideCapability.get("osgi.serviceloader")).isNotNull(); + assertThat(provideCapability.get("osgi.serviceloader") + .get("osgi.serviceloader")).isEqualTo("com.example.service.Type"); + assertThat(provideCapability.get("osgi.serviceloader")) + .containsEntry("osgi.serviceloader", "com.example.service.Type") + .containsEntry("register:", "java.lang.String"); + + Parameters requireCapability = manifest.getRequireCapability(); + Requirement req = CapReqBuilder.getRequirementsFrom(requireCapability) + .get(0); + assertEquals("osgi.extender", req.getNamespace()); + assertEquals("optional", req.getDirectives() + .get("resolution")); + assertEquals("(&(osgi.extender=osgi.serviceloader.registrar)(version>=1.0.0)(!(version>=2.0.0)))", + req.getDirectives() + .get("filter")); + + System.out.println(provideCapability.toString() + .replace(',', '\n')); + System.out.println(requireCapability.toString() + .replace(',', '\n')); + } + } + + @SuppressWarnings("unchecked") + @Test + public void testInvalidServiceImplementationNamesShouldBeIgnored() throws Exception { + try (Builder b = new Builder();) { + b.addClasspath(IO.getFile("bin_test")); + b.setProperty("-includeresource", """ + META-INF/services/com.example.service.Type;\ + literal='\ + key=value' + """); + b.setProperty("-metainf-services", "auto"); + b.build(); + assertFalse(b.check()); + List errors = b.getErrors(); + assertEquals("analyzer processing annotation key=value but the associated class is not found in the JAR", + errors.get(0)); + Domain manifest = Domain.domain(b.getJar() + .getManifest()); + Parameters provideCapability = manifest.getProvideCapability(); + + assertThat(provideCapability.get("osgi.service")).isNull(); + + Parameters requireCapability = manifest.getRequireCapability(); + + System.out.println(provideCapability.toString() + .replace(',', '\n')); + System.out.println(requireCapability.toString() + .replace(',', '\n')); } } + } diff --git a/biz.aQute.bndlib/src/aQute/bnd/help/Syntax.java b/biz.aQute.bndlib/src/aQute/bnd/help/Syntax.java index 790a337723..569f157200 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/help/Syntax.java +++ b/biz.aQute.bndlib/src/aQute/bnd/help/Syntax.java @@ -736,7 +736,11 @@ null, null, new Syntax("name", "The display name of the developer", "name='Peter WORKINGSET + "=Implementations, Drivers", null, null), new Syntax("-x-overwritestrategy", "On windows we sometimes cannot delete a file because someone holds a lock in our or another process. So if we set the -overwritestrategy flag we use an avoiding strategy.", - "-x-overwritestrategy=gc", "(classic|delay|gc|windows-only-disposable-names|disposable-names)", null) + "-x-overwritestrategy=gc", "(classic|delay|gc|windows-only-disposable-names|disposable-names)", null), + new Syntax(METAINF_SERVICES, "Controls how META-INF/services files are processed.", METAINF_SERVICES + ": auto", + "(" + METAINF_SERVICES_STRATEGY_ANNOTATION + "|" + METAINF_SERVICES_STRATEGY_AUTO + "|" + + METAINF_SERVICES_STRATEGY_NONE + ")", + Pattern.compile("auto|annotation|none")) }; final static Map, Pattern> BASE_PATTERNS = Maps.ofEntries( diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java index 5002d8ebb6..ff0d4f7402 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java @@ -136,6 +136,7 @@ public class Analyzer extends Processor { private final Descriptors descriptors = new Descriptors(); private final List classpath = list(); private final Map classspace = map(); + private final Map lookAsideClasses = map(); private final Map importedClassesCache = map(); private boolean analyzed = false; private boolean diagnostics = false; @@ -2188,7 +2189,6 @@ Set findProvidedPackages() throws Exception { return providers; } - boolean isProvider(TypeRef t) { if (t == null || t.isJava()) return false; @@ -3111,6 +3111,10 @@ public Clazz findClass(TypeRef typeRef) throws Exception { if (c != null) return c; + c = lookAsideClasses.get(typeRef); + if (c != null) + return c; + Resource r = findResource(typeRef.getPath()); if (r == null) { getClass().getClassLoader(); @@ -3904,7 +3908,7 @@ public void addDelta(Jar delta) { public void addAnnotation(Annotation ann, TypeRef c) throws Exception { Clazz clazz = findClass(c); if (clazz == null) { - error("analyzer processing annotation %s but the associated class %s is not found in the JAR", c); + error("analyzer processing annotation %s but the associated class is not found in the JAR", c); return; } annotationHeaders.classStart(clazz); @@ -3912,4 +3916,34 @@ public void addAnnotation(Annotation ann, TypeRef c) throws Exception { annotationHeaders.classEnd(); } + /** + * Get a class from our own class path + * + * @param type a local type on the bnd classpath + */ + public void addClasspathDefault(Class type) { + assert type != null : "type must be given"; + + try { + TypeRef ref = getTypeRefFrom(type); + URL resource = type.getClassLoader() + .getResource(ref.getPath()); + if (resource == null) { + error("analyzer.addclasspathdefault expected class %s to be on the classpath since we have a type", + type); + } else { + + Resource r = new URLResource(resource, null); + Clazz c = new Clazz(this, ref.getFQN(), r); + if (c != null) { + c.parseClassFile(); + // we don't want that class in our classspace + lookAsideClasses.putIfAbsent(ref, c); + } + } + } catch (Exception e) { + error("analyzer.findclassorlocal Failed to read a class from the bnd classpath"); + } + } + } diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java index 2c52a44797..2e5e102f7a 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java @@ -309,6 +309,14 @@ public interface Constants { String WORKINGSET_MEMBER = "member"; String REQUIRE_BND = "-require-bnd"; + /* + * processing of META-INF/services folder section. + */ + String METAINF_SERVICES = "-metainf-services"; + String METAINF_SERVICES_STRATEGY_ANNOTATION = "annotation"; + String METAINF_SERVICES_STRATEGY_AUTO = "auto"; + String METAINF_SERVICES_STRATEGY_NONE = "none"; + // Deprecated String CLASSPATH = "-classpath"; String OUTPUT = "-output"; @@ -329,7 +337,7 @@ public interface Constants { CONNECTION_SETTINGS, RUNPROVIDEDCAPABILITIES, WORKINGSET, RUNSTORAGE, REPRODUCIBLE, INCLUDEPACKAGE, CDIANNOTATIONS, REMOTEWORKSPACE, MAVEN_DEPENDENCIES, BUILDERIGNORE, STALECHECK, MAVEN_SCOPE, RUNSTARTLEVEL, RUNOPTIONS, NOCLASSFORNAME, EXPORT_APIGUARDIAN, RESOLVE, DEFINE_CONTRACT, GENERATE, RUNFRAMEWORKRESTART, - NOIMPORTJAVA, VERSIONDEFAULTS, LIBRARY); + NOIMPORTJAVA, VERSIONDEFAULTS, LIBRARY, METAINF_SERVICES); // Ignore bundle specific headers. These headers do not make a lot of sense // to inherit @@ -567,6 +575,7 @@ public interface Constants { String SERVICELOADER_REGISTER_DIRECTIVE = "register:"; String SERVICELOADER_NAMESPACE = "osgi.serviceloader"; + /** * Launch constants that should be shared by launchers */ @@ -602,6 +611,7 @@ public interface Constants { String INTERNAL_PREFIX = "-internal-"; + /* * Deprecated Section */ diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java index fd10c7393c..fe1f44da4b 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java @@ -1,12 +1,23 @@ package aQute.bnd.osgi.metainf; +import static aQute.bnd.osgi.Constants.METAINF_SERVICES; +import static aQute.bnd.osgi.Constants.METAINF_SERVICES_STRATEGY_ANNOTATION; +import static aQute.bnd.osgi.Constants.METAINF_SERVICES_STRATEGY_AUTO; +import static aQute.bnd.osgi.Constants.METAINF_SERVICES_STRATEGY_NONE; + import java.lang.annotation.RetentionPolicy; +import java.util.Collection; +import java.util.LinkedHashSet; import java.util.Map; +import aQute.bnd.annotation.Resolution; +import aQute.bnd.annotation.spi.ServiceProvider; import aQute.bnd.header.Attrs; +import aQute.bnd.header.Parameters; import aQute.bnd.osgi.Analyzer; import aQute.bnd.osgi.Annotation; import aQute.bnd.osgi.Annotation.ElementType; +import aQute.bnd.osgi.Constants; import aQute.bnd.osgi.Descriptors.TypeRef; import aQute.bnd.osgi.Processor; import aQute.bnd.osgi.metainf.MetaInfService.Implementation; @@ -14,7 +25,8 @@ /** * process the META-INF/services/* files. These files can contain bnd - * annotations. + * annotations. Use instruction {@link Constants#METAINF_SERVICES} to control + * this. */ public class MetaInfServiceParser implements AnalyzerPlugin { @@ -25,18 +37,77 @@ public class MetaInfServiceParser implements AnalyzerPlugin { */ @Override public boolean analyzeJar(Analyzer analyzer) throws Exception { - MetaInfService.getServiceFiles(analyzer.getJar()) - .values() + + String strategy = strategy(analyzer); + switch (strategy) { + case METAINF_SERVICES_STRATEGY_NONE : + return false; + case METAINF_SERVICES_STRATEGY_AUTO : { + analyzer.addClasspathDefault(ServiceProvider.class); + break; + } + case METAINF_SERVICES_STRATEGY_ANNOTATION : { + analyzer.addClasspathDefault(ServiceProvider.class); + break; + } + } + + Collection allServices = MetaInfService.getServiceFiles(analyzer.getJar()) + .values(); + + // "auto" applies only to services without any annotation at all. so + // divide them + Collection withAnnotations = new LinkedHashSet<>(); + Collection withoutAnnotations = new LinkedHashSet<>(); + + allServices.forEach(mis -> { + mis.getImplementations() + .values() + .forEach(impl -> { + Parameters annotations = impl.getAnnotations(); + + if (!annotations.isEmpty()) { + withAnnotations.add(mis); + } else { + withoutAnnotations.add(mis); + } + + }); + }); + + if (METAINF_SERVICES_STRATEGY_AUTO.equals(strategy)) { + withoutAnnotations + .stream() + .flatMap(mis -> mis.getImplementations() + .values() + .stream()) + .forEach(impl -> { + + Parameters annotations = new Parameters(); + Attrs attrs1 = new Attrs(); + attrs1.put(Constants.RESOLUTION_DIRECTIVE, Resolution.OPTIONAL); + attrs1.addDirectiveAliases(); + annotations.add(ServiceProvider.class.getName(), attrs1); + + annotations.forEach((annotationName, attrs) -> { + doAnnotationsforMetaInf(analyzer, impl, Processor.removeDuplicateMarker(annotationName), attrs); + }); + }); + } + + withAnnotations .stream() .flatMap(mis -> mis.getImplementations() .values() .stream()) .forEach(impl -> { - impl.getAnnotations() - .forEach((annotationName, attrs) -> { - doAnnotationsforMetaInf(analyzer, impl, Processor.removeDuplicateMarker(annotationName), attrs); - }); + Parameters annotations = impl.getAnnotations(); + + annotations.forEach((annotationName, attrs) -> { + doAnnotationsforMetaInf(analyzer, impl, Processor.removeDuplicateMarker(annotationName), attrs); + }); }); + return false; } @@ -57,4 +128,7 @@ private void doAnnotationsforMetaInf(Analyzer analyzer, Implementation impl, Str } } + private String strategy(Analyzer analyzer) { + return analyzer.getProperty(METAINF_SERVICES, METAINF_SERVICES_STRATEGY_ANNOTATION); + } } diff --git a/bndtools.core/src/bndtools/editor/completion/BndHover.java b/bndtools.core/src/bndtools/editor/completion/BndHover.java index f6787989b1..82aa169249 100644 --- a/bndtools.core/src/bndtools/editor/completion/BndHover.java +++ b/bndtools.core/src/bndtools/editor/completion/BndHover.java @@ -63,7 +63,12 @@ public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) { if (syntax != null) { sb.append(syntax.getLead()); - sb.append("\nE.g. "); + String values = syntax.getValues(); + if (values != null && !values.isBlank()) { + sb.append("\nValues: "); + sb.append(syntax.getValues()); + } + sb.append("\nExample: "); sb.append(syntax.getExample()); } Parameters decorated = properties.decorated(key); diff --git a/docs/_chapters/230-manifest-annotations.md b/docs/_chapters/230-manifest-annotations.md index 14ddecde52..a6a253dae2 100644 --- a/docs/_chapters/230-manifest-annotations.md +++ b/docs/_chapters/230-manifest-annotations.md @@ -28,9 +28,10 @@ Export-Package: com.acme;version="1.3.4" ### META-INF/services Annotations -Some developers do not want to rely on the bnd/OSGi annotations. For this reason, it is possible to also apply the annotations to the files in the `META-INF/services`. These Service Loader files have the name of a Service Loader _service_ and contain the names of the implementation classes. One fully qualified name per line. +Some developers do not want to rely on the additional dependency of bnd/OSGi annotations. For this reason, it is possible to also apply the annotations textually in comments to the files in the `META-INF/services`. +This trick avoids a compile time dependency. These Service Loader files have the name of a Service Loader _service_ and contain the names of the implementation classes. One fully qualified name per line. -The annotations can be applied in the comments. To make them more readable, it is possible to import the fully qualified name of the annotation. +To make these annotations in the comments more readable, it is possible to import the fully qualified name of the annotation. ``` META-INF/services/com.example.ServiceType: @@ -42,7 +43,11 @@ META-INF/services/com.example.ServiceType: ``` The processing is identical to the normal class based annotation processing. The `#class` macro will be set to the implementation class. The `#value` will be set in all cases to the service type unless overridden. -Clearly using the class annotation is far superior: +This behavior can be controlled with the [-metainf-services](/instructions/metainf-services.html) instruction. Default is `annotation` which processes the textual annotations above, while the other convenience strategy `auto` automatically creates `Provide-Capability` headers for services without any textual annotations. + +
+ +While the above is a compromise, clearly using the real class annotation is far superior: * Help from the IDE * Impossible to make typos @@ -52,6 +57,7 @@ The analysis of these files happens after the analyzer plugins have been run. Th Since the annotations & imports happen in the comments, it is not possible to diagnose any errors. If the comment does not match its regular expression, it will be silently ignored. + ### @Requirement & @Capability Annotations Though Java class files contain enough information to find code dependencies, there are many dependencies that are indirect. OSGi _extenders_ for instance are often a requirement to make a bundle function correctly but often client bundles have no code dependency on the extender. For example, Declarative Services (DS) went out of its way to allow components to be Plain Old Java Objects (POJO). The result is that resolving a closure of bundles starting from a DS client bundle would not drag in the Service Component Runtime (SCR), resulting in a satisfied but rather idle closure. diff --git a/docs/_instructions/metainf-services.md b/docs/_instructions/metainf-services.md new file mode 100644 index 0000000000..235c030b3a --- /dev/null +++ b/docs/_instructions/metainf-services.md @@ -0,0 +1,42 @@ +--- +layout: default +class: Analyzer +title: -metainf-services +summary: Controls how META-INF/services files are processed. +--- + +The `-metainf-services` instruction tells **bnd** how files in the `META-INF/services` should be processed. +See the chapter about [META-INF/services Annotations](/chapters/230-manifest-annotations.html#meta-infservices-annotations) how to apply Bundle annotations to the files in the `META-INF/services`. + +This instruction can have the following values: + +- `annotation` (default if not set): Scan META-INF/services files and only process those files which contain at least one textual annotations. +- `auto`: The convenience strategy. Scans `META-INF/services` files and auto-registers implementations in services files which do not have a single textual annotation. The latter means that bnd behaves as if there was a `aQute.bnd.annotation.spi.ServiceProvider` annotation present on each implementation. This is useful if you just want to have bnd generate the `Provide-Capability` headers for `osgi.serviceloader`. Additionally `auto` behaves like `annotation` and would process all other files with textual annotations. +- `none`: skip processing of files in `META-INF/services` completely + +## Example of auto-registration + +Assume we want to wrap a non-OSGi library containing `META-INF/services`. +Create `bnd.bnd` file and put a library having a `META-INF/services` folder e.g. [groovy-3.0.22.jar](https://mvnrepository.com/artifact/org.codehaus.groovy/groovy/3.0.22) in a local `lib` folder. +We will use `-includeresource: @lib/groovy-3.0.22.jar` to [unroll the jar](/instructions/includeresource.html#rolling). + +``` +# bnd.bnd +-includeresource: @lib/groovy-3.0.22.jar +-metainf-services: auto +``` + +This creates a new jar with the following MANIFEST.MF headers from the `META-INF/services` folder: + +``` +# MANIFEST.MF +Provide-Capability osgi.service;objectClass:List="org.codehaus.groovy.transform.ASTTransformation";effective:=active + osgi.serviceloader;osgi.serviceloader="org.codehaus.groovy.transform.ASTTransformation";register:="groovy.grape.GrabAnnotationTransformation" +Require-Capability osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))" + osgi.extender;filter:="(&(osgi.extender=osgi.serviceloader.registrar)(version>=1.0.0)(!(version>=2.0.0)))";resolution:=optional +``` + +Because `-metainf-services: auto` is used, it instructs bnd to auto-generate a `@ServiceProvider` annotation under the hood for services without annotations. +To prevent the latter (auto-generation) use the default `-metainf-services: annotation` (to process only textual annotations) or use `-metainf-services: none` to skip processing of `META-INF/services` files completely. + +[source](https://github.com/bndtools/bnd/blob/master/biz.aQute.bndlib/src/aQute/bnd/osgi/metainf/MetaInfServiceParser.java) \ No newline at end of file diff --git a/docs/css/style.scss b/docs/css/style.scss index c4911fe274..0e4d0f46f0 100644 --- a/docs/css/style.scss +++ b/docs/css/style.scss @@ -196,4 +196,8 @@ ul .side-nav-section{ .side-nav{ padding-top: 1px !important; +} + +pre.highlight { + overflow: auto; } \ No newline at end of file