Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

auto register capabilities for META-INF/services #6309

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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