ARIES-1933 manual opt-in
Signed-off-by: Raymond Augé <rotty3000@apache.org>
diff --git a/spi-fly/pom.xml b/spi-fly/pom.xml
index 46b345b..3a49544 100644
--- a/spi-fly/pom.xml
+++ b/spi-fly/pom.xml
@@ -31,7 +31,7 @@
<groupId>org.apache.aries.spifly</groupId>
<artifactId>spifly</artifactId>
<name>Apache Aries SPI Fly</name>
- <version>1.2.5-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<packaging>pom</packaging>
<description>
SPI support for OSGi
diff --git a/spi-fly/spi-fly-core/pom.xml b/spi-fly/spi-fly-core/pom.xml
index f2dc9f0..e5715aa 100644
--- a/spi-fly/spi-fly-core/pom.xml
+++ b/spi-fly/spi-fly-core/pom.xml
@@ -30,7 +30,7 @@
<groupId>org.apache.aries.spifly</groupId>
<artifactId>org.apache.aries.spifly.core-internal</artifactId>
- <version>1.2.5-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Apache Aries SPI Fly Core (internal module)</name>
<description>
diff --git a/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/BaseActivator.java b/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/BaseActivator.java
index f295ae4..75e592b 100644
--- a/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/BaseActivator.java
+++ b/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/BaseActivator.java
@@ -19,6 +19,7 @@
package org.apache.aries.spifly;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -28,6 +29,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.SortedMap;
@@ -45,6 +47,10 @@
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.util.tracker.BundleTracker;
+import aQute.bnd.header.Parameters;
+import aQute.bnd.stream.MapStream;
+import aQute.libg.glob.Glob;
+
public abstract class BaseActivator implements BundleActivator {
private static final Set<WeavingData> NON_WOVEN_BUNDLE = Collections.emptySet();
private static final Logger logger = Logger.getLogger(BaseActivator.class.getName());
@@ -59,6 +65,8 @@
private BundleTracker consumerBundleTracker;
@SuppressWarnings("rawtypes")
private BundleTracker providerBundleTracker;
+ private Optional<Parameters> autoConsumerInstructions;
+ private Optional<Parameters> autoProviderInstructions;
private final ConcurrentMap<Bundle, Set<WeavingData>> bundleWeavingData =
new ConcurrentHashMap<Bundle, Set<WeavingData>>();
@@ -73,6 +81,19 @@
public synchronized void start(BundleContext context, final String consumerHeaderName) throws Exception {
bundleContext = context;
+ try {
+ autoConsumerInstructions = Optional.ofNullable(
+ bundleContext.getProperty("org.apache.aries.spifly.auto.consumers")
+ ).map(Parameters::new);
+
+ autoProviderInstructions = Optional.ofNullable(
+ bundleContext.getProperty("org.apache.aries.spifly.auto.providers")
+ ).map(Parameters::new);
+ }
+ catch (Throwable t) {
+ logger.log(Level.SEVERE, t.getMessage(), t);
+ }
+
providerBundleTracker = new BundleTracker(context,
Bundle.ACTIVE, new ProviderBundleTrackerCustomizer(this, context.getBundle()));
providerBundleTracker.open();
@@ -95,8 +116,25 @@
}
Map<String, List<String>> allHeaders = new HashMap<String, List<String>>();
- allHeaders.put(consumerHeaderName, getAllHeaders(consumerHeaderName, bundle));
- allHeaders.put(SpiFlyConstants.REQUIRE_CAPABILITY, getAllHeaders(SpiFlyConstants.REQUIRE_CAPABILITY, bundle));
+ Set<String> addedHeaders = new HashSet<String>();
+ List<String> added = allHeaders.put(consumerHeaderName, getAllHeaders(consumerHeaderName, bundle));
+ if (added != null) {
+ added.stream().forEach(addedHeaders::add);
+ }
+ added = allHeaders.put(SpiFlyConstants.REQUIRE_CAPABILITY, getAllHeaders(SpiFlyConstants.REQUIRE_CAPABILITY, bundle));
+ if (added != null) {
+ added.stream().forEach(addedHeaders::add);
+ }
+ if (addedHeaders.isEmpty()) {
+ getAutoConsumerInstructions().map(Parameters::stream).orElseGet(MapStream::empty).filterKey(
+ i -> Glob.toPattern(i).asPredicate().test(bundle.getSymbolicName())
+ ).findFirst().ifPresent(
+ un -> allHeaders.put(
+ SpiFlyConstants.REQUIRE_CAPABILITY,
+ Arrays.asList(
+ SpiFlyConstants.CLIENT_REQUIREMENT.concat(",osgi.serviceloader;filter:='(osgi.serviceloader=*)'")))
+ );
+ }
Set<WeavingData> wd = new HashSet<WeavingData>();
for (Map.Entry<String, List<String>> entry : allHeaders.entrySet()) {
@@ -217,12 +255,20 @@
}
public void registerProviderBundle(String registrationClassName, Bundle bundle, Map<String, Object> customAttributes) {
- registrationClassName = registrationClassName.trim();
- registeredProviders.putIfAbsent(registrationClassName,
- Collections.synchronizedSortedMap(new TreeMap<Long, Pair<Bundle, Map<String, Object>>>()));
+ SortedMap<Long, Pair<Bundle, Map<String, Object>>> map = registeredProviders.computeIfAbsent(registrationClassName,
+ k -> Collections.synchronizedSortedMap(new TreeMap<Long, Pair<Bundle, Map<String, Object>>>()));
- SortedMap<Long, Pair<Bundle, Map<String, Object>>> map = registeredProviders.get(registrationClassName);
- map.put(bundle.getBundleId(), new Pair<Bundle, Map<String, Object>>(bundle, customAttributes));
+ map.compute(
+ bundle.getBundleId(),
+ (k,v) -> {
+ if (v == null) {
+ return new Pair<Bundle, Map<String, Object>>(bundle, customAttributes);
+ }
+ else {
+ v.getRight().putAll(customAttributes);
+ return v;
+ }
+ });
}
public void unregisterProviderBundle(Bundle bundle) {
@@ -288,6 +334,24 @@
return Collections.emptySet();
}
+ public Optional<Parameters> getAutoConsumerInstructions() {
+ if (autoConsumerInstructions == null) return Optional.empty();
+ return autoConsumerInstructions;
+ }
+
+ public void setAutoConsumerInstructions(Optional<Parameters> autoConsumerInstructions) {
+ this.autoConsumerInstructions = autoConsumerInstructions;
+ }
+
+ public Optional<Parameters> getAutoProviderInstructions() {
+ if (autoProviderInstructions == null) return Optional.empty();
+ return autoProviderInstructions;
+ }
+
+ public void setAutoProviderInstructions(Optional<Parameters> autoProviderInstructions) {
+ this.autoProviderInstructions = autoProviderInstructions;
+ }
+
private Collection<Bundle> getBundles(List<BundleDescriptor> descriptors, String className, String methodName,
Map<Pair<Integer, String>, String> args) {
if (descriptors == null) {
diff --git a/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizer.java b/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizer.java
index 9a50b48..28b9b9a 100644
--- a/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizer.java
+++ b/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizer.java
@@ -18,23 +18,27 @@
*/
package org.apache.aries.spifly;
+import static java.util.stream.Collectors.toList;
+
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
+import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Objects;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.logging.Level;
+import java.util.stream.Stream;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleEvent;
@@ -50,6 +54,8 @@
import aQute.bnd.header.Attrs;
import aQute.bnd.header.OSGiHeader;
import aQute.bnd.header.Parameters;
+import aQute.bnd.stream.MapStream;
+import aQute.libg.glob.Glob;
/**
* Listens for new bundles being installed and registers them as service providers if applicable.
@@ -57,6 +63,9 @@
@SuppressWarnings("rawtypes")
public class ProviderBundleTrackerCustomizer implements BundleTrackerCustomizer {
private static final String METAINF_SERVICES = "META-INF/services";
+ private static final List<String> MERGE_HEADERS = Arrays.asList(
+ Constants.IMPORT_PACKAGE, Constants.REQUIRE_BUNDLE, Constants.EXPORT_PACKAGE,
+ Constants.PROVIDE_CAPABILITY, Constants.REQUIRE_CAPABILITY);
final BaseActivator activator;
final Bundle spiBundle;
@@ -68,12 +77,13 @@
@Override
public List<ServiceRegistration> addingBundle(final Bundle bundle, BundleEvent event) {
- log(Level.FINE, "Bundle Considered for SPI providers: "
- + bundle.getSymbolicName());
-
if (bundle.equals(spiBundle))
return null; // don't process the SPI bundle itself
+ log(Level.FINE, "Bundle Considered for SPI providers: "
+ + bundle.getSymbolicName());
+
+ DiscoveryMode discoveryMode = DiscoveryMode.SERVICELOADER_CAPABILITIES;
List<String> providedServices = null;
Map<String, Object> customAttributes = new HashMap<String, Object>();
if (bundle.getHeaders().get(SpiFlyConstants.REQUIRE_CAPABILITY) != null) {
@@ -84,22 +94,28 @@
}
}
- boolean fromSPIProviderHeader = false;
String spiProviderHeader = getHeaderFromBundleOrFragment(bundle, SpiFlyConstants.SPI_PROVIDER_HEADER);
if (providedServices == null && spiProviderHeader != null) {
String header = spiProviderHeader.trim();
if ("*".equals(header)) {
providedServices = new ArrayList<String>();
} else {
- providedServices = Arrays.asList(header.split(","));
+ providedServices = Stream.of(header.split(",")).map(String::trim).collect(toList());
}
- fromSPIProviderHeader = true;
+ discoveryMode = DiscoveryMode.SPI_PROVIDER_HEADER;
+ }
+
+ List<URL> serviceFileURLs = null;
+ if (providedServices == null) {
+ Entry<List<String>, List<URL>> autoServices = getFromAutoProviderProperty(bundle, customAttributes);
+
+ providedServices = autoServices.getKey();
+ serviceFileURLs = autoServices.getValue();
+ discoveryMode = DiscoveryMode.AUTO_PROVIDERS_PROPERTY;
}
if (providedServices == null) {
- log(Level.FINE, "No '"
- + SpiFlyConstants.SPI_PROVIDER_HEADER
- + "' Manifest header. Skipping bundle: "
+ log(Level.FINE, "No provided SPI services. Skipping bundle: "
+ bundle.getSymbolicName());
return null;
} else {
@@ -107,33 +123,65 @@
+ bundle.getSymbolicName());
}
- for (String svc : providedServices) {
+ for (String serviceType : providedServices) {
// Eagerly register any services that are explicitly listed, as they may not be found in META-INF/services
- activator.registerProviderBundle(svc, bundle, customAttributes);
+ activator.registerProviderBundle(serviceType, bundle, customAttributes);
}
- List<URL> serviceFileURLs = new ArrayList<URL>();
-
- Enumeration<URL> entries = bundle.findEntries(METAINF_SERVICES, "*", false);
- if (entries != null) {
- serviceFileURLs.addAll(Collections.list(entries));
- }
-
- Object bcp = bundle.getHeaders().get(Constants.BUNDLE_CLASSPATH);
- if (bcp instanceof String) {
- for (String entry : ((String) bcp).split(",")) {
- entry = entry.trim();
- if (entry.equals("."))
- continue;
-
- URL url = bundle.getResource(entry);
- if (url != null) {
- serviceFileURLs.addAll(getMetaInfServiceURLsFromJar(url));
- }
- }
+ if (serviceFileURLs == null) {
+ serviceFileURLs = getServiceFileUrls(bundle);
}
final List<ServiceRegistration> registrations = new ArrayList<ServiceRegistration>();
+ for (ServiceDetails details : collectServiceDetails(bundle, serviceFileURLs, discoveryMode)) {
+ if (providedServices.size() > 0 && !providedServices.contains(details.serviceType))
+ continue;
+
+ try {
+ final Class<?> cls = bundle.loadClass(details.instanceType);
+ log(Level.INFO, "Loaded SPI provider: " + cls);
+
+ if (details.properties != null) {
+ ServiceRegistration reg = null;
+ Object instance =
+ (details.properties.containsKey("service.scope") &&
+ "prototype".equalsIgnoreCase(String.valueOf(details.properties.get("service.scope")))) ?
+ new ProviderPrototypeServiceFactory(cls) :
+ new ProviderServiceFactory(cls);
+
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ if (bundle.hasPermission(new ServicePermission(details.serviceType, ServicePermission.REGISTER))) {
+ reg = bundle.getBundleContext().registerService(
+ details.serviceType, instance, details.properties);
+ } else {
+ log(Level.INFO, "Bundle " + bundle + " does not have the permission to register services of type: " + details.serviceType);
+ }
+ } else {
+ reg = bundle.getBundleContext().registerService(
+ details.serviceType, instance, details.properties);
+ }
+
+ if (reg != null) {
+ registrations.add(reg);
+ log(Level.INFO, "Registered service: " + reg);
+ }
+ }
+
+ activator.registerProviderBundle(details.serviceType, bundle, details.properties);
+ log(Level.INFO, "Registered provider " + details.instanceType + " of service " + details.serviceType + " in bundle " + bundle.getSymbolicName());
+ } catch (Exception e) {
+ log(Level.WARNING,
+ "Could not load provider " + details.instanceType + " of service " + details.serviceType, e);
+ }
+ }
+
+ return registrations;
+ }
+
+ private List<ServiceDetails> collectServiceDetails(Bundle bundle, List<URL> serviceFileURLs, DiscoveryMode discoveryMode) {
+ List<ServiceDetails> serviceDetails = new ArrayList<>();
+
for (URL serviceFileURL : serviceFileURLs) {
log(Level.INFO, "Found SPI resource: " + serviceFileURL);
@@ -158,48 +206,30 @@
registrationClassName = serviceFile.substring(idx + 1);
}
- if (providedServices.size() > 0 && !providedServices.contains(registrationClassName))
- continue;
-
- final Class<?> cls = bundle.loadClass(className);
- log(Level.INFO, "Loaded SPI provider: " + cls);
-
final Hashtable<String, Object> properties;
- if (fromSPIProviderHeader)
+ if (discoveryMode == DiscoveryMode.SPI_PROVIDER_HEADER) {
properties = new Hashtable<String, Object>();
- else
- properties = findServiceRegistrationProperties(bundle.getHeaders(), registrationClassName, className);
+ }
+ else if (discoveryMode == DiscoveryMode.AUTO_PROVIDERS_PROPERTY) {
+ properties = activator.getAutoProviderInstructions().map(
+ Parameters::stream
+ ).orElseGet(MapStream::empty).filterKey(
+ i -> Glob.toPattern(i).asPredicate().test(bundle.getSymbolicName())
+ ).values().findFirst().map(
+ Hashtable<String, Object>::new
+ ).orElseGet(() -> new Hashtable<String, Object>());
+ }
+ else {
+ properties = findServiceRegistrationProperties(bundle, registrationClassName, className);
+ }
if (properties != null) {
properties.put(SpiFlyConstants.SERVICELOADER_MEDIATOR_PROPERTY, spiBundle.getBundleId());
- properties.put(SpiFlyConstants.PROVIDER_IMPLCLASS_PROPERTY, cls.getName());
-
- ServiceRegistration reg = null;
- Object instance = (properties.containsKey("service.scope") && "prototype".equals(properties.get("service.scope"))) ?
- new ProviderPrototypeServiceFactory(cls) :
- new ProviderServiceFactory(cls);
-
- SecurityManager sm = System.getSecurityManager();
- if (sm != null) {
- if (bundle.hasPermission(new ServicePermission(registrationClassName, ServicePermission.REGISTER))) {
- reg = bundle.getBundleContext().registerService(
- registrationClassName, instance, properties);
- } else {
- log(Level.INFO, "Bundle " + bundle + " does not have the permission to register services of type: " + registrationClassName);
- }
- } else {
- reg = bundle.getBundleContext().registerService(
- registrationClassName, instance, properties);
- }
-
- if (reg != null) {
- registrations.add(reg);
- log(Level.INFO, "Registered service: " + reg);
- }
+ properties.put(SpiFlyConstants.PROVIDER_IMPLCLASS_PROPERTY, className);
+ properties.put(SpiFlyConstants.PROVIDER_DISCOVERY_MODE, discoveryMode.toString());
}
- activator.registerProviderBundle(registrationClassName, bundle, customAttributes);
- log(Level.INFO, "Registered provider: " + registrationClassName + " in bundle " + bundle.getSymbolicName());
+ serviceDetails.add(new ServiceDetails(registrationClassName, className, properties));
} catch (Exception e) {
log(Level.WARNING,
"Could not load SPI implementation referred from " + serviceFileURL, e);
@@ -210,7 +240,53 @@
}
}
- return registrations;
+ return serviceDetails;
+ }
+ private Entry<List<String>, List<URL>> getFromAutoProviderProperty(Bundle bundle, Map<String, Object> customAttributes) {
+ return activator.getAutoProviderInstructions().map(
+ Parameters::stream
+ ).orElseGet(MapStream::empty).filterKey(
+ i -> Glob.toPattern(i).asPredicate().test(bundle.getSymbolicName())
+ ).values().findFirst().map(
+ un -> {
+ List<URL> serviceFileURLs = getServiceFileUrls(bundle);
+
+ List<ServiceDetails> collectServiceDetails = collectServiceDetails(bundle, serviceFileURLs, DiscoveryMode.AUTO_PROVIDERS_PROPERTY);
+
+ collectServiceDetails.stream().map(ServiceDetails::getProperties).filter(Objects::nonNull).forEach(
+ hashtable -> hashtable.forEach(customAttributes::put)
+ );
+
+ List<String> providedServices = collectServiceDetails.stream().map(ServiceDetails::getServiceType).collect(toList());
+
+ return new AbstractMap.SimpleImmutableEntry<>(providedServices, serviceFileURLs);
+ }
+ ).orElseGet(() -> new AbstractMap.SimpleImmutableEntry<>(null, null));
+ }
+
+ private List<URL> getServiceFileUrls(Bundle bundle) {
+ List<URL> serviceFileURLs = new ArrayList<URL>();
+
+ Enumeration<URL> entries = bundle.findEntries(METAINF_SERVICES, "*", false);
+ if (entries != null) {
+ serviceFileURLs.addAll(Collections.list(entries));
+ }
+
+ Object bcp = bundle.getHeaders().get(Constants.BUNDLE_CLASSPATH);
+ if (bcp instanceof String) {
+ for (String entry : ((String) bcp).split(",")) {
+ entry = entry.trim();
+ if (entry.equals("."))
+ continue;
+
+ URL url = bundle.getResource(entry);
+ if (url != null) {
+ serviceFileURLs.addAll(getMetaInfServiceURLsFromJar(url));
+ }
+ }
+ }
+
+ return serviceFileURLs;
}
private String getHeaderFromBundleOrFragment(Bundle bundle, String headerName) {
@@ -218,9 +294,10 @@
}
private String getHeaderFromBundleOrFragment(Bundle bundle, String headerName, String matchString) {
- String val = bundle.getHeaders().get(headerName);
- if (matches(val, matchString))
- return val;
+ Parameters headerParameters = new Parameters(bundle.getHeaders().get(headerName));
+ if (matches(headerParameters.toString(), matchString) && !MERGE_HEADERS.contains(headerName)) {
+ return headerParameters.isEmpty() ? null : headerParameters.toString();
+ }
BundleRevision rev = bundle.adapt(BundleRevision.class);
if (rev != null) {
@@ -228,15 +305,22 @@
if (wiring != null) {
for (BundleWire wire : wiring.getProvidedWires("osgi.wiring.host")) {
Bundle fragment = wire.getRequirement().getRevision().getBundle();
- val = fragment.getHeaders().get(headerName);
- if (matches(val, matchString)) {
- return val;
+ Parameters fragmentParameters = new Parameters(fragment.getHeaders().get(headerName));
+ if (MERGE_HEADERS.contains(headerName)) {
+ headerParameters.mergeWith(fragmentParameters, false);
+ }
+ else {
+ headerParameters = fragmentParameters;
+ }
+
+ if (matches(headerParameters.toString(), matchString)) {
+ return headerParameters.toString();
}
}
}
}
- return null;
+ return headerParameters.isEmpty() ? null : headerParameters.toString();
}
private boolean matches(String val, String matchString) {
@@ -275,7 +359,7 @@
for (Entry<String, ? extends Map<String, String>> serviceLoaderCapability : ConsumerHeaderProcessor.findAllMetadata(capabilities, SpiFlyConstants.SERVICELOADER_CAPABILITY_NAMESPACE)) {
for (Entry<String, String> entry : serviceLoaderCapability.getValue().entrySet()) {
if (SpiFlyConstants.SERVICELOADER_CAPABILITY_NAMESPACE.equals(entry.getKey())) {
- serviceNames.add(entry.getValue().toString());
+ serviceNames.add(entry.getValue().trim());
continue;
}
if (SpiFlyConstants.REGISTER_DIRECTIVE.equals(entry.getKey()) && entry.getValue().equals("")) {
@@ -290,8 +374,8 @@
// null means don't register,
// otherwise the return value should be taken as the service registration properties
- private Hashtable<String, Object> findServiceRegistrationProperties(Dictionary<?,?> headers, String spiName, String implName) {
- Object capabilityHeader = headers.get(SpiFlyConstants.PROVIDE_CAPABILITY);
+ private Hashtable<String, Object> findServiceRegistrationProperties(Bundle bundle, String spiName, String implName) {
+ Object capabilityHeader = getHeaderFromBundleOrFragment(bundle, SpiFlyConstants.PROVIDE_CAPABILITY);
if (capabilityHeader == null)
return null;
@@ -354,7 +438,8 @@
@Override
public void modifiedBundle(Bundle bundle, BundleEvent event, Object registrations) {
- // should really be doing something here...
+ removedBundle(bundle, event, registrations);
+ addingBundle(bundle, event);
}
@Override
@@ -378,4 +463,36 @@
private void log(Level level, String message, Throwable th) {
activator.log(level, message, th);
}
+
+ enum DiscoveryMode {
+ SPI_PROVIDER_HEADER,
+ AUTO_PROVIDERS_PROPERTY,
+ SERVICELOADER_CAPABILITIES
+ }
+
+ class ServiceDetails {
+ public ServiceDetails(String serviceType, String instanceType, Hashtable<String, Object> properties) {
+ this.serviceType = serviceType;
+ this.instanceType = instanceType;
+ this.properties = properties;
+ }
+ public String getInstanceType() {
+ return instanceType;
+ }
+ public Hashtable<String, Object> getProperties() {
+ return properties;
+ }
+ public String getServiceType() {
+ return serviceType;
+ }
+ @Override
+ public String toString() {
+ return String.format(
+ "ServiceDetails [serviceType=\"%s\", instanceType=\"%s\", properties=%s]",
+ getServiceType(), getInstanceType(), getProperties());
+ }
+ private final String instanceType;
+ private final Hashtable<String, Object> properties;
+ private final String serviceType;
+ }
}
diff --git a/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/SpiFlyConstants.java b/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/SpiFlyConstants.java
index 629c2fa..afdf77b 100644
--- a/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/SpiFlyConstants.java
+++ b/spi-fly/spi-fly-core/src/main/java/org/apache/aries/spifly/SpiFlyConstants.java
@@ -41,6 +41,7 @@
// Service registration property
String SERVICELOADER_MEDIATOR_PROPERTY = "serviceloader.mediator";
String PROVIDER_IMPLCLASS_PROPERTY = ".org.apache.aries.spifly.provider.implclass";
+ String PROVIDER_DISCOVERY_MODE = ".org.apache.aries.spifly.provider.discovery.mode";
// The names of the extenders involved
String PROCESSOR_EXTENDER_NAME = "osgi.serviceloader.processor";
diff --git a/spi-fly/spi-fly-core/src/test/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizerGenericCapabilityTest.java b/spi-fly/spi-fly-core/src/test/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizerGenericCapabilityTest.java
index d8c6ea0..9740260 100644
--- a/spi-fly/spi-fly-core/src/test/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizerGenericCapabilityTest.java
+++ b/spi-fly/spi-fly-core/src/test/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizerGenericCapabilityTest.java
@@ -35,6 +35,7 @@
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import org.apache.aries.mytest.MySPI;
@@ -56,6 +57,8 @@
import org.osgi.framework.wiring.BundleWire;
import org.osgi.framework.wiring.BundleWiring;
+import aQute.bnd.header.Parameters;
+
public class ProviderBundleTrackerCustomizerGenericCapabilityTest {
@Test
public void testAddingRemovedBundle() throws Exception {
@@ -190,11 +193,109 @@
assertSame(implBundle, bundles.iterator().next());
Map<String, Object> attrs = activator.getCustomBundleAttributes("org.apache.aries.mytest.MySPI", implBundle);
- assertEquals(1, attrs.size());
+ assertEquals(4, attrs.size());
assertEquals("yeah", attrs.get("approval"));
}
@Test
+ public void testAutoProviderSystemProperty() throws Exception {
+ Bundle mediatorBundle = EasyMock.createMock(Bundle.class);
+ EasyMock.expect(mediatorBundle.getBundleId()).andReturn(42l).anyTimes();
+ EasyMock.replay(mediatorBundle);
+ BaseActivator activator = new BaseActivator() {
+ @Override
+ public void start(BundleContext context) throws Exception {}
+ };
+
+ activator.setAutoProviderInstructions(Optional.of(new Parameters("*")));
+ ProviderBundleTrackerCustomizer customizer = new ProviderBundleTrackerCustomizer(activator, mediatorBundle);
+
+ @SuppressWarnings("rawtypes")
+ ServiceRegistration sreg = EasyMock.createMock(ServiceRegistration.class);
+ EasyMock.replay(sreg);
+
+ BundleContext implBC = mockSPIBundleContext(sreg);
+ Dictionary<String, String> headers = new Hashtable<String, String>();
+ Bundle implBundle = mockSPIBundle(implBC, headers);
+
+ @SuppressWarnings("rawtypes")
+ List<ServiceRegistration> registrations = customizer.addingBundle(implBundle, null);
+ assertEquals(1, registrations.size());
+ Collection<Bundle> bundles = activator.findProviderBundles("org.apache.aries.mytest.MySPI");
+ assertEquals(1, bundles.size());
+ assertSame(implBundle, bundles.iterator().next());
+
+ Map<String, Object> attrs = activator.getCustomBundleAttributes("org.apache.aries.mytest.MySPI", implBundle);
+ assertEquals(3, attrs.size());
+ assertNull(attrs.get("approval"));
+ }
+
+ @Test
+ public void testAutoProviderSystemPropertyPlusProperty() throws Exception {
+ Bundle mediatorBundle = EasyMock.createMock(Bundle.class);
+ EasyMock.expect(mediatorBundle.getBundleId()).andReturn(42l).anyTimes();
+ EasyMock.replay(mediatorBundle);
+ BaseActivator activator = new BaseActivator() {
+ @Override
+ public void start(BundleContext context) throws Exception {}
+ };
+
+ activator.setAutoProviderInstructions(Optional.of(new Parameters("*;approval=yeah")));
+ ProviderBundleTrackerCustomizer customizer = new ProviderBundleTrackerCustomizer(activator, mediatorBundle);
+
+ @SuppressWarnings("rawtypes")
+ ServiceRegistration sreg = EasyMock.createMock(ServiceRegistration.class);
+ EasyMock.replay(sreg);
+
+ BundleContext implBC = mockSPIBundleContext(sreg);
+ Dictionary<String, String> headers = new Hashtable<String, String>();
+ Bundle implBundle = mockSPIBundle(implBC, headers);
+
+ @SuppressWarnings("rawtypes")
+ List<ServiceRegistration> registrations = customizer.addingBundle(implBundle, null);
+ assertEquals(1, registrations.size());
+ Collection<Bundle> bundles = activator.findProviderBundles("org.apache.aries.mytest.MySPI");
+ assertEquals(1, bundles.size());
+ assertSame(implBundle, bundles.iterator().next());
+
+ Map<String, Object> attrs = activator.getCustomBundleAttributes("org.apache.aries.mytest.MySPI", implBundle);
+ assertEquals(4, attrs.size());
+ assertEquals("yeah", attrs.get("approval"));
+ }
+
+ @Test
+ public void testAutoProviderSystemPropertyTargetBundle() throws Exception {
+ Bundle mediatorBundle = EasyMock.createMock(Bundle.class);
+ EasyMock.expect(mediatorBundle.getBundleId()).andReturn(42l).anyTimes();
+ EasyMock.replay(mediatorBundle);
+ BaseActivator activator = new BaseActivator() {
+ @Override
+ public void start(BundleContext context) throws Exception {}
+ };
+
+ activator.setAutoProviderInstructions(Optional.of(new Parameters("bsn")));
+ ProviderBundleTrackerCustomizer customizer = new ProviderBundleTrackerCustomizer(activator, mediatorBundle);
+
+ @SuppressWarnings("rawtypes")
+ ServiceRegistration sreg = EasyMock.createMock(ServiceRegistration.class);
+ EasyMock.replay(sreg);
+
+ BundleContext implBC = mockSPIBundleContext(sreg);
+ Dictionary<String, String> headers = new Hashtable<String, String>();
+ Bundle implBundle = mockSPIBundle(implBC, headers);
+
+ @SuppressWarnings("rawtypes")
+ List<ServiceRegistration> registrations = customizer.addingBundle(implBundle, null);
+ assertEquals(1, registrations.size());
+ Collection<Bundle> bundles = activator.findProviderBundles("org.apache.aries.mytest.MySPI");
+ assertEquals(1, bundles.size());
+ assertSame(implBundle, bundles.iterator().next());
+
+ Map<String, Object> attrs = activator.getCustomBundleAttributes("org.apache.aries.mytest.MySPI", implBundle);
+ assertEquals(3, attrs.size());
+ }
+
+ @Test
public void testNonServiceRegistryBundle() throws Exception {
Bundle mediatorBundle = EasyMock.createMock(Bundle.class);
EasyMock.expect(mediatorBundle.getBundleId()).andReturn(42l).anyTimes();
@@ -501,6 +602,7 @@
Bundle implBundle = EasyMock.createNiceMock(Bundle.class);
EasyMock.expect(implBundle.getBundleContext()).andReturn(implBC).anyTimes();
EasyMock.expect(implBundle.getHeaders()).andReturn(headers).anyTimes();
+ EasyMock.expect(implBundle.getSymbolicName()).andReturn("bsn").anyTimes();
// List the resources found at META-INF/services in the test bundle
URL dir = getClass().getResource("impl1/META-INF/services");
diff --git a/spi-fly/spi-fly-core/src/test/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizerTest.java b/spi-fly/spi-fly-core/src/test/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizerTest.java
index 0381ad4..ae7a6b5 100644
--- a/spi-fly/spi-fly-core/src/test/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizerTest.java
+++ b/spi-fly/spi-fly-core/src/test/java/org/apache/aries/spifly/ProviderBundleTrackerCustomizerTest.java
@@ -41,15 +41,17 @@
import org.osgi.framework.ServiceRegistration;
public class ProviderBundleTrackerCustomizerTest {
+
+ private BaseActivator activator = new BaseActivator() {
+ @Override
+ public void start(BundleContext context) throws Exception {}
+ };
+
@Test
public void testAddingRemovedBundle() throws Exception {
Bundle mediatorBundle = EasyMock.createMock(Bundle.class);
EasyMock.expect(mediatorBundle.getBundleId()).andReturn(42l).anyTimes();
EasyMock.replay(mediatorBundle);
- BaseActivator activator = new BaseActivator() {
- @Override
- public void start(BundleContext context) throws Exception {}
- };
ProviderBundleTrackerCustomizer customizer = new ProviderBundleTrackerCustomizer(activator, mediatorBundle);
@@ -82,7 +84,7 @@
BundleContext implBC = mockSPIBundleContext(EasyMock.createNiceMock(ServiceRegistration.class));
Bundle spiBundle = mockSPIBundle(implBC);
- ProviderBundleTrackerCustomizer customizer = new ProviderBundleTrackerCustomizer(EasyMock.createNiceMock(BaseActivator.class), spiBundle);
+ ProviderBundleTrackerCustomizer customizer = new ProviderBundleTrackerCustomizer(activator, spiBundle);
assertNull("The SpiFly bundle itself should be ignored", customizer.addingBundle(spiBundle, null));
}
@@ -91,7 +93,7 @@
BundleContext implBC = mockSPIBundleContext(EasyMock.createNiceMock(ServiceRegistration.class));
Bundle implBundle = mockSPIBundle(implBC, null);
- ProviderBundleTrackerCustomizer customizer = new ProviderBundleTrackerCustomizer(EasyMock.createNiceMock(BaseActivator.class), null);
+ ProviderBundleTrackerCustomizer customizer = new ProviderBundleTrackerCustomizer(activator, null);
assertNull("Bundle doesn't opt-in so should be ignored", customizer.addingBundle(implBundle, null));
}
@@ -101,10 +103,6 @@
Bundle mediatorBundle = EasyMock.createMock(Bundle.class);
EasyMock.expect(mediatorBundle.getBundleId()).andReturn(42l).anyTimes();
EasyMock.replay(mediatorBundle);
- BaseActivator activator = new BaseActivator() {
- @Override
- public void start(BundleContext context) throws Exception {}
- };
ProviderBundleTrackerCustomizer customizer = new ProviderBundleTrackerCustomizer(activator, mediatorBundle);
@@ -165,7 +163,7 @@
EasyMock.expect(spiBundle.getBundleId()).andReturn(25l).anyTimes();
EasyMock.replay(spiBundle);
- ProviderBundleTrackerCustomizer customizer = new ProviderBundleTrackerCustomizer(EasyMock.createNiceMock(BaseActivator.class), spiBundle);
+ ProviderBundleTrackerCustomizer customizer = new ProviderBundleTrackerCustomizer(activator, spiBundle);
assertEquals(2, customizer.addingBundle(implBundle, null).size());
}
diff --git a/spi-fly/spi-fly-dynamic-bundle/pom.xml b/spi-fly/spi-fly-dynamic-bundle/pom.xml
index f0d8f7e..5a596b6 100644
--- a/spi-fly/spi-fly-dynamic-bundle/pom.xml
+++ b/spi-fly/spi-fly-dynamic-bundle/pom.xml
@@ -30,7 +30,7 @@
<groupId>org.apache.aries.spifly</groupId>
<artifactId>org.apache.aries.spifly.dynamic.bundle</artifactId>
- <version>1.2.5-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<name>Apache Aries SPI Fly Dynamic Weaving Bundle</name>
<description>
diff --git a/spi-fly/spi-fly-dynamic-bundle/resolve.bndrun b/spi-fly/spi-fly-dynamic-bundle/resolve.bndrun
index cfa422e..5682aa8 100644
--- a/spi-fly/spi-fly-dynamic-bundle/resolve.bndrun
+++ b/spi-fly/spi-fly-dynamic-bundle/resolve.bndrun
@@ -15,4 +15,4 @@
-runee: JavaSE-1.8
-runfw: org.apache.felix.framework
-runrequires: osgi.identity;filter:='(osgi.identity=${project.artifactId})'
--runbundles: org.apache.aries.spifly.dynamic.bundle;version='[1.2.4,1.2.5)'
+-runbundles: org.apache.aries.spifly.dynamic.bundle;version='[1.3.0,1.3.1)'
diff --git a/spi-fly/spi-fly-dynamic-bundle/src/test/java/org/apache/aries/spifly/dynamic/ClientWeavingHookGenericCapabilityTest.java b/spi-fly/spi-fly-dynamic-bundle/src/test/java/org/apache/aries/spifly/dynamic/ClientWeavingHookGenericCapabilityTest.java
index dd9525a..2afd2c8 100644
--- a/spi-fly/spi-fly-dynamic-bundle/src/test/java/org/apache/aries/spifly/dynamic/ClientWeavingHookGenericCapabilityTest.java
+++ b/spi-fly/spi-fly-dynamic-bundle/src/test/java/org/apache/aries/spifly/dynamic/ClientWeavingHookGenericCapabilityTest.java
@@ -39,6 +39,7 @@
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -62,6 +63,8 @@
import org.osgi.framework.wiring.BundleWire;
import org.osgi.framework.wiring.BundleWiring;
+import aQute.bnd.header.Parameters;
+
public class ClientWeavingHookGenericCapabilityTest {
DynamicWeavingActivator activator;
@@ -78,6 +81,74 @@
}
@Test
+ public void testAutoConsumerSystemProperty() throws Exception {
+ Dictionary<String, String> consumerHeaders = new Hashtable<String, String>();
+
+ // Register the bundle that provides the SPI implementation.
+ Bundle providerBundle = mockProviderBundle("impl1", 1);
+ activator.registerProviderBundle("org.apache.aries.mytest.MySPI", providerBundle, new HashMap<String, Object>());
+
+ Bundle consumerBundle = mockConsumerBundle(consumerHeaders, providerBundle);
+ activator.setAutoConsumerInstructions(Optional.of(new Parameters(consumerBundle.getSymbolicName())));
+ activator.addConsumerWeavingData(consumerBundle, SpiFlyConstants.REQUIRE_CAPABILITY);
+
+ Bundle spiFlyBundle = mockSpiFlyBundle("spifly", Version.parseVersion("1.9.4"), consumerBundle, providerBundle);
+ WeavingHook wh = new ClientWeavingHook(spiFlyBundle.getBundleContext(), activator);
+
+ // Weave the TestClient class.
+ URL clsUrl = getClass().getResource("TestClient.class");
+ Assert.assertNotNull("Precondition", clsUrl);
+
+ String clientClassName = "org.apache.aries.spifly.dynamic.TestClient";
+ WovenClass wc = new MyWovenClass(clsUrl, clientClassName, consumerBundle);
+ Assert.assertEquals("Precondition", 0, wc.getDynamicImports().size());
+ wh.weave(wc);
+ Assert.assertEquals(1, wc.getDynamicImports().size());
+ String di1 = "org.apache.aries.spifly";
+ String di = wc.getDynamicImports().get(0);
+ Assert.assertTrue("Weaving should have added a dynamic import", di1.equals(di));
+
+ // Invoke the woven class and check that it properly sets the TCCL so that the
+ // META-INF/services/org.apache.aries.mytest.MySPI file from impl1 is visible.
+ Class<?> cls = wc.getDefinedClass();
+ Method method = cls.getMethod("test", new Class [] {String.class});
+ Object result = method.invoke(cls.newInstance(), "hello");
+ Assert.assertEquals(Collections.singleton("olleh"), result);
+ }
+
+ @Test
+ public void testAutoConsumerSystemProperty_negative() throws Exception {
+ Dictionary<String, String> consumerHeaders = new Hashtable<String, String>();
+
+ // Register the bundle that provides the SPI implementation.
+ Bundle providerBundle = mockProviderBundle("impl1", 1);
+ activator.registerProviderBundle("org.apache.aries.mytest.MySPI", providerBundle, new HashMap<String, Object>());
+
+ Bundle consumerBundle = mockConsumerBundle(consumerHeaders, providerBundle);
+ activator.addConsumerWeavingData(consumerBundle, SpiFlyConstants.REQUIRE_CAPABILITY);
+
+ Bundle spiFlyBundle = mockSpiFlyBundle("spifly", Version.parseVersion("1.9.4"), consumerBundle, providerBundle);
+ WeavingHook wh = new ClientWeavingHook(spiFlyBundle.getBundleContext(), activator);
+
+ // Weave the TestClient class.
+ URL clsUrl = getClass().getResource("TestClient.class");
+ Assert.assertNotNull("Precondition", clsUrl);
+
+ String clientClassName = "org.apache.aries.spifly.dynamic.TestClient";
+ WovenClass wc = new MyWovenClass(clsUrl, clientClassName, consumerBundle);
+ Assert.assertEquals("Precondition", 0, wc.getDynamicImports().size());
+ wh.weave(wc);
+ Assert.assertEquals(0, wc.getDynamicImports().size());
+
+ // Invoke the woven class and check that it properly sets the TCCL so that the
+ // META-INF/services/org.apache.aries.mytest.MySPI file from impl1 is visible.
+ Class<?> cls = wc.getDefinedClass();
+ Method method = cls.getMethod("test", new Class [] {String.class});
+ Object result = method.invoke(cls.newInstance(), "hello");
+ Assert.assertEquals(Collections.emptySet(), result);
+ }
+
+ @Test
public void testBasicServiceLoaderUsage() throws Exception {
Dictionary<String, String> consumerHeaders = new Hashtable<String, String>();
consumerHeaders.put(SpiFlyConstants.REQUIRE_CAPABILITY, SpiFlyConstants.CLIENT_REQUIREMENT);
diff --git a/spi-fly/spi-fly-dynamic-framework-extension/pom.xml b/spi-fly/spi-fly-dynamic-framework-extension/pom.xml
index 6d27954..97b4680 100644
--- a/spi-fly/spi-fly-dynamic-framework-extension/pom.xml
+++ b/spi-fly/spi-fly-dynamic-framework-extension/pom.xml
@@ -30,7 +30,7 @@
<groupId>org.apache.aries.spifly</groupId>
<artifactId>org.apache.aries.spifly.dynamic.framework.extension</artifactId>
- <version>1.2.5-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<name>Apache Aries SPI Fly Dynamic Weaving Framework Extension</name>
<description>
@@ -49,7 +49,7 @@
<properties>
<asm.version>7.3.1</asm.version>
<maven.bundle.plugin.version>4.2.0</maven.bundle.plugin.version>
- <bnd.version>5.0.0</bnd.version>
+ <bnd.version>5.0.1</bnd.version>
</properties>
<dependencies>
diff --git a/spi-fly/spi-fly-dynamic-framework-extension/resolve.bndrun b/spi-fly/spi-fly-dynamic-framework-extension/resolve.bndrun
index 4ffeba2..4a7b3c0 100644
--- a/spi-fly/spi-fly-dynamic-framework-extension/resolve.bndrun
+++ b/spi-fly/spi-fly-dynamic-framework-extension/resolve.bndrun
@@ -15,4 +15,4 @@
-runee: JavaSE-1.8
-runfw: org.apache.felix.framework
-runrequires: osgi.identity;filter:='(osgi.identity=${project.artifactId})'
--runbundles: org.apache.aries.spifly.dynamic.framework.extension;version='[1.2.4,1.2.5)'
+-runbundles: org.apache.aries.spifly.dynamic.framework.extension;version='[1.3.0,1.3.1)'
diff --git a/spi-fly/spi-fly-static-bundle/pom.xml b/spi-fly/spi-fly-static-bundle/pom.xml
index c6394fa..8a2fda7 100644
--- a/spi-fly/spi-fly-static-bundle/pom.xml
+++ b/spi-fly/spi-fly-static-bundle/pom.xml
@@ -30,7 +30,7 @@
<groupId>org.apache.aries.spifly</groupId>
<artifactId>org.apache.aries.spifly.static.bundle</artifactId>
- <version>1.2.5-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<name>Apache Aries SPI Fly Static Weaving Bundle</name>
<description>
@@ -121,6 +121,7 @@
aQute.service.reporter
</_conditionalpackage>
<_includeresource>
+ @biz.aQute.bndlib-[0-9.]*.jar!/aQute/bnd/osgi/Instruction*.class,
META-INF/LICENSE=LICENSE,
META-INF/NOTICE=NOTICE,
</_includeresource>
diff --git a/spi-fly/spi-fly-static-bundle/resolve.bndrun b/spi-fly/spi-fly-static-bundle/resolve.bndrun
index 454eae2..3917f05 100644
--- a/spi-fly/spi-fly-static-bundle/resolve.bndrun
+++ b/spi-fly/spi-fly-static-bundle/resolve.bndrun
@@ -15,4 +15,4 @@
-runee: JavaSE-1.8
-runfw: org.apache.felix.framework
-runrequires: osgi.identity;filter:='(osgi.identity=${project.artifactId})'
--runbundles: org.apache.aries.spifly.static.bundle;version='[1.2.4,1.2.5)'
+-runbundles: org.apache.aries.spifly.static.bundle;version='[1.3.0,1.3.1)'
diff --git a/spi-fly/spi-fly-static-tool/pom.xml b/spi-fly/spi-fly-static-tool/pom.xml
index 49bc43a..e1c6c1f 100644
--- a/spi-fly/spi-fly-static-tool/pom.xml
+++ b/spi-fly/spi-fly-static-tool/pom.xml
@@ -30,7 +30,7 @@
<groupId>org.apache.aries.spifly</groupId>
<artifactId>org.apache.aries.spifly.static.tool</artifactId>
- <version>1.2.5-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Apache Aries SPI Fly Static Weaving Tool</name>
<description>
diff --git a/spi-fly/spi-fly-weaver/pom.xml b/spi-fly/spi-fly-weaver/pom.xml
index b963749..5f46d3c 100644
--- a/spi-fly/spi-fly-weaver/pom.xml
+++ b/spi-fly/spi-fly-weaver/pom.xml
@@ -30,7 +30,7 @@
<groupId>org.apache.aries.spifly</groupId>
<artifactId>org.apache.aries.spifly.weaver-internal</artifactId>
- <version>1.2.5-SNAPSHOT</version>
+ <version>1.3.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Apache Aries SPI Fly Weaver (internal module)</name>