blob: 28b9b9aa2b57ec881f1b5c94e9b5c1806a5093db [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
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.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;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServicePermission;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleWire;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.util.tracker.BundleTrackerCustomizer;
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.
*/
@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;
public ProviderBundleTrackerCustomizer(BaseActivator activator, Bundle spiBundle) {
this.activator = activator;
this.spiBundle = spiBundle;
}
@Override
public List<ServiceRegistration> addingBundle(final Bundle bundle, BundleEvent event) {
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) {
try {
providedServices = readServiceLoaderMediatorCapabilityMetadata(bundle, customAttributes);
} catch (InvalidSyntaxException e) {
log(Level.SEVERE, "Unable to read capabilities from bundle " + bundle, e);
}
}
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 = Stream.of(header.split(",")).map(String::trim).collect(toList());
}
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 provided SPI services. Skipping bundle: "
+ bundle.getSymbolicName());
return null;
} else {
log(Level.INFO, "Examining bundle for SPI provider: "
+ bundle.getSymbolicName());
}
for (String serviceType : providedServices) {
// Eagerly register any services that are explicitly listed, as they may not be found in META-INF/services
activator.registerProviderBundle(serviceType, bundle, customAttributes);
}
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);
try {
BufferedReader reader = new BufferedReader(
new InputStreamReader(serviceFileURL.openStream()));
String className = null;
while((className = reader.readLine()) != null) {
try {
className = className.trim();
if (className.length() == 0)
continue; // empty line
if (className.startsWith("#"))
continue; // a comment
String serviceFile = serviceFileURL.toExternalForm();
int idx = serviceFile.lastIndexOf('/');
String registrationClassName = className;
if (serviceFile.length() > idx) {
registrationClassName = serviceFile.substring(idx + 1);
}
final Hashtable<String, Object> properties;
if (discoveryMode == DiscoveryMode.SPI_PROVIDER_HEADER) {
properties = new Hashtable<String, Object>();
}
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, className);
properties.put(SpiFlyConstants.PROVIDER_DISCOVERY_MODE, discoveryMode.toString());
}
serviceDetails.add(new ServiceDetails(registrationClassName, className, properties));
} catch (Exception e) {
log(Level.WARNING,
"Could not load SPI implementation referred from " + serviceFileURL, e);
}
}
} catch (IOException e) {
log(Level.WARNING, "Could not read SPI metadata from " + serviceFileURL, e);
}
}
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) {
return getHeaderFromBundleOrFragment(bundle, headerName, null);
}
private String getHeaderFromBundleOrFragment(Bundle bundle, String headerName, String matchString) {
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) {
BundleWiring wiring = rev.getWiring();
if (wiring != null) {
for (BundleWire wire : wiring.getProvidedWires("osgi.wiring.host")) {
Bundle fragment = wire.getRequirement().getRevision().getBundle();
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 headerParameters.isEmpty() ? null : headerParameters.toString();
}
private boolean matches(String val, String matchString) {
if (val == null)
return false;
if (matchString == null)
return true;
int idx = val.indexOf(matchString);
return idx >= 0;
}
// An empty list returned means 'all SPIs'
// A return value of null means no SPIs
// A populated list means: only these SPIs
private List<String> readServiceLoaderMediatorCapabilityMetadata(Bundle bundle, Map<String, Object> customAttributes) throws InvalidSyntaxException {
String requirementHeader = getHeaderFromBundleOrFragment(bundle, SpiFlyConstants.REQUIRE_CAPABILITY, SpiFlyConstants.SERVICELOADER_CAPABILITY_NAMESPACE);
if (requirementHeader == null)
return null;
Parameters requirements = OSGiHeader.parseHeader(requirementHeader);
Entry<String, ? extends Map<String, String>> extenderRequirement = ConsumerHeaderProcessor.findRequirement(requirements, SpiFlyConstants.EXTENDER_CAPABILITY_NAMESPACE, SpiFlyConstants.REGISTRAR_EXTENDER_NAME);
if (extenderRequirement == null)
return null;
Parameters capabilities;
String capabilityHeader = getHeaderFromBundleOrFragment(bundle, SpiFlyConstants.PROVIDE_CAPABILITY, SpiFlyConstants.SERVICELOADER_CAPABILITY_NAMESPACE);
if (capabilityHeader == null) {
capabilities = new Parameters();
} else {
capabilities = OSGiHeader.parseHeader(capabilityHeader);
}
List<String> serviceNames = new ArrayList<String>();
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().trim());
continue;
}
if (SpiFlyConstants.REGISTER_DIRECTIVE.equals(entry.getKey()) && entry.getValue().equals("")) {
continue;
}
customAttributes.put(entry.getKey(), entry.getValue());
}
}
return serviceNames;
}
// null means don't register,
// otherwise the return value should be taken as the service registration properties
private Hashtable<String, Object> findServiceRegistrationProperties(Bundle bundle, String spiName, String implName) {
Object capabilityHeader = getHeaderFromBundleOrFragment(bundle, SpiFlyConstants.PROVIDE_CAPABILITY);
if (capabilityHeader == null)
return null;
Parameters capabilities = OSGiHeader.parseHeader(capabilityHeader.toString());
for (Map.Entry<String, Attrs> entry : capabilities.entrySet()) {
String key = ConsumerHeaderProcessor.removeDuplicateMarker(entry.getKey());
Attrs attrs = entry.getValue();
if (!SpiFlyConstants.SERVICELOADER_CAPABILITY_NAMESPACE.equals(key))
continue;
if (!attrs.containsKey(SpiFlyConstants.SERVICELOADER_CAPABILITY_NAMESPACE) ||
!attrs.get(SpiFlyConstants.SERVICELOADER_CAPABILITY_NAMESPACE).equals(spiName))
continue;
if (attrs.containsKey(SpiFlyConstants.REGISTER_DIRECTIVE) &&
!attrs.get(SpiFlyConstants.REGISTER_DIRECTIVE).equals(implName))
continue;
Hashtable<String, Object> properties = new Hashtable<String, Object>();
for (Map.Entry<String, String> prop : attrs.entrySet()) {
if (SpiFlyConstants.SERVICELOADER_CAPABILITY_NAMESPACE.equals(prop.getKey()) ||
SpiFlyConstants.REGISTER_DIRECTIVE.equals(prop.getKey()) ||
key.startsWith("."))
continue;
properties.put(prop.getKey(), prop.getValue());
}
return properties;
}
return null;
}
private List<URL> getMetaInfServiceURLsFromJar(URL url) {
List<URL> urls = new ArrayList<URL>();
try {
JarInputStream jis = null;
try {
jis = new JarInputStream(url.openStream());
JarEntry je = null;
while((je = jis.getNextJarEntry()) != null) {
if (je.getName().startsWith(METAINF_SERVICES) &&
je.getName().length() > (METAINF_SERVICES.length() + 1)) {
urls.add(new URL("jar:" + url + "!/" + je.getName()));
}
}
} finally {
if (jis != null) {
jis.close();
}
}
} catch (IOException e) {
log(Level.SEVERE, "Problem opening embedded jar file: " + url, e);
}
return urls;
}
@Override
public void modifiedBundle(Bundle bundle, BundleEvent event, Object registrations) {
removedBundle(bundle, event, registrations);
addingBundle(bundle, event);
}
@Override
@SuppressWarnings("unchecked")
public void removedBundle(Bundle bundle, BundleEvent event, Object registrations) {
activator.unregisterProviderBundle(bundle);
if (registrations == null)
return;
for (ServiceRegistration reg : (List<ServiceRegistration>) registrations) {
reg.unregister();
log(Level.INFO, "Unregistered: " + reg);
}
}
private void log(Level level, String message) {
activator.log(level, message);
}
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;
}
}