Skip to content

Commit

Permalink
Merge pull request #6309 from chrisrueger/6304-auto-register-metainf-…
Browse files Browse the repository at this point in the history
…services

auto register capabilities for META-INF/services
  • Loading branch information
chrisrueger authored Oct 10, 2024
2 parents 74e9214 + e52fcde commit ff4bb1d
Show file tree
Hide file tree
Showing 10 changed files with 423 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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();) {
Expand Down Expand Up @@ -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()
Expand All @@ -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");
}
Expand All @@ -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()
Expand Down Expand Up @@ -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();) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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<String> 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'));
}
}


}
6 changes: 5 additions & 1 deletion biz.aQute.bndlib/src/aQute/bnd/help/Syntax.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Class<?>, Pattern> BASE_PATTERNS = Maps.ofEntries(
Expand Down
38 changes: 36 additions & 2 deletions biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ public class Analyzer extends Processor {
private final Descriptors descriptors = new Descriptors();
private final List<Jar> classpath = list();
private final Map<TypeRef, Clazz> classspace = map();
private final Map<TypeRef, Clazz> lookAsideClasses = map();
private final Map<TypeRef, Clazz> importedClassesCache = map();
private boolean analyzed = false;
private boolean diagnostics = false;
Expand Down Expand Up @@ -2188,7 +2189,6 @@ Set<PackageRef> findProvidedPackages() throws Exception {
return providers;
}


boolean isProvider(TypeRef t) {
if (t == null || t.isJava())
return false;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -3904,12 +3908,42 @@ 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);
annotationHeaders.annotation(ann);
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");
}
}

}
Loading

0 comments on commit ff4bb1d

Please sign in to comment.