/**
* 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 java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
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.jar.JarEntry;
import java.util.jar.JarInputStream;
import org.apache.aries.util.manifest.ManifestHeaderProcessor;
import org.apache.aries.util.manifest.ManifestHeaderProcessor.GenericMetadata;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
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.service.log.LogService;
import org.osgi.util.tracker.BundleTrackerCustomizer;
/**
* Listens for new bundles being installed and registers them as service providers if applicable.
*/
public class ProviderBundleTrackerCustomizer implements BundleTrackerCustomizer {
private static final String METAINF_SERVICES = "META-INF/services";
final BaseActivator activator;
final Bundle spiBundle;
public ProviderBundleTrackerCustomizer(BaseActivator activator, Bundle spiBundle) {
this.activator = activator;
this.spiBundle = spiBundle;
}
public List<ServiceRegistration> addingBundle(final Bundle bundle, BundleEvent event) {
log(LogService.LOG_DEBUG, "Bundle Considered for SPI providers: "
+ bundle.getSymbolicName());
if (bundle.equals(spiBundle))
return null; // don't process the SPI bundle itself
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(LogService.LOG_ERROR, "Unable to read capabilities from bundle " + bundle, e);
}
}
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(","));
}
fromSPIProviderHeader = true;
}
if (providedServices == null) {
log(LogService.LOG_DEBUG, "No '"
+ SpiFlyConstants.SPI_PROVIDER_HEADER
+ "' Manifest header. Skipping bundle: "
+ bundle.getSymbolicName());
return null;
} else {
log(LogService.LOG_INFO, "Examining bundle for SPI provider: "
+ bundle.getSymbolicName());
}
for (String svc : providedServices) {
// Eagerly register any services that are explicitly listed, as they may not be found in META-INF/services
activator.registerProviderBundle(svc, bundle, customAttributes);
}
List<URL> serviceFileURLs = new ArrayList<URL>();
@SuppressWarnings("unchecked")
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));
}
}
}
final List<ServiceRegistration> registrations = new ArrayList<ServiceRegistration>();
for (URL serviceFileURL : serviceFileURLs) {
log(LogService.LOG_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);
}
if (providedServices.size() > 0 && !providedServices.contains(registrationClassName))
continue;
final Class<?> cls = bundle.loadClass(className);
log(LogService.LOG_INFO, "Loaded SPI provider: " + cls);
final Hashtable<String, Object> properties;
if (fromSPIProviderHeader)
properties = new Hashtable<String, Object>();
else
properties = findServiceRegistrationProperties(bundle.getHeaders(), registrationClassName, className);
if (properties != null) {
properties.put(SpiFlyConstants.SERVICELOADER_MEDIATOR_PROPERTY, spiBundle.getBundleId());
properties.put(SpiFlyConstants.PROVIDER_IMPLCLASS_PROPERTY, cls.getName());
ServiceRegistration reg = null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
if (bundle.hasPermission(new ServicePermission(registrationClassName, ServicePermission.REGISTER))) {
reg = bundle.getBundleContext().registerService(
registrationClassName, new ProviderServiceFactory(cls), properties);
} else {
log(LogService.LOG_INFO, "Bundle " + bundle + " does not have the permission to register services of type: " + registrationClassName);
}
} else {
reg = bundle.getBundleContext().registerService(
registrationClassName, new ProviderServiceFactory(cls), properties);
}
if (reg != null) {
registrations.add(reg);
log(LogService.LOG_INFO, "Registered service: " + reg);
}
}
activator.registerProviderBundle(registrationClassName, bundle, customAttributes);
log(LogService.LOG_INFO, "Registered provider: " + registrationClassName + " in bundle " + bundle.getSymbolicName());
} catch (Exception e) {
log(LogService.LOG_WARNING,
"Could not load SPI implementation referred from " + serviceFileURL, e);
}
}
} catch (IOException e) {
log(LogService.LOG_WARNING, "Could not read SPI metadata from " + serviceFileURL, e);
}
}
return registrations;
}
private String getHeaderFromBundleOrFragment(Bundle bundle, String headerName) {
return getHeaderFromBundleOrFragment(bundle, headerName, null);
}
private String getHeaderFromBundleOrFragment(Bundle bundle, String headerName, String matchString) {
String val = bundle.getHeaders().get(headerName);
if (matches(val, matchString))
return val;
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();
val = fragment.getHeaders().get(headerName);
if (matches(val, matchString)) {
return val;
}
}
}
}
return null;
}
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;
List<GenericMetadata> requirements = ManifestHeaderProcessor.parseRequirementString(requirementHeader);
GenericMetadata extenderRequirement = findRequirement(requirements, SpiFlyConstants.EXTENDER_CAPABILITY_NAMESPACE, SpiFlyConstants.REGISTRAR_EXTENDER_NAME);
if (extenderRequirement == null)
return null;
List<GenericMetadata> capabilities;
String capabilityHeader = getHeaderFromBundleOrFragment(bundle, SpiFlyConstants.PROVIDE_CAPABILITY, SpiFlyConstants.SERVICELOADER_CAPABILITY_NAMESPACE);
if (capabilityHeader == null) {
capabilities = Collections.emptyList();
} else {
capabilities = ManifestHeaderProcessor.parseCapabilityString(capabilityHeader);
}
List<String> serviceNames = new ArrayList<String>();
for (GenericMetadata serviceLoaderCapability : findAllMetadata(capabilities, SpiFlyConstants.SERVICELOADER_CAPABILITY_NAMESPACE)) {
for (Map.Entry<String, Object> entry : serviceLoaderCapability.getAttributes().entrySet()) {
if (SpiFlyConstants.SERVICELOADER_CAPABILITY_NAMESPACE.equals(entry.getKey())) {
serviceNames.add(entry.getValue().toString());
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(Dictionary<?,?> headers, String spiName, String implName) {
Object capabilityHeader = headers.get(SpiFlyConstants.PROVIDE_CAPABILITY);
if (capabilityHeader == null)
return null;
List<GenericMetadata> capabilities = ManifestHeaderProcessor.parseCapabilityString(capabilityHeader.toString());
GenericMetadata cap = findCapability(capabilities, SpiFlyConstants.SERVICELOADER_CAPABILITY_NAMESPACE, spiName);
Hashtable<String, Object> properties = new Hashtable<String, Object>();
if (cap != null) {
for (Map.Entry<String, Object> entry : cap.getAttributes().entrySet()) {
if (SpiFlyConstants.SERVICELOADER_CAPABILITY_NAMESPACE.equals(entry.getKey()))
continue;
if (!entry.getKey().startsWith("."))
properties.put(entry.getKey(), entry.getValue());
}
}
String registerDirective = cap.getDirectives().get(SpiFlyConstants.REGISTER_DIRECTIVE);
if (registerDirective == null) {
return properties;
} else {
if ("".equals(registerDirective.trim()))
return null;
if (implName.equals(registerDirective.trim()))
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(LogService.LOG_ERROR, "Problem opening embedded jar file: " + url, e);
}
return urls;
}
private GenericMetadata findCapability(List<GenericMetadata> capabilities, String namespace, String spiName) {
for (GenericMetadata cap : capabilities) {
if (namespace.equals(cap.getNamespace())) {
if (spiName.equals(cap.getAttributes().get(namespace))) {
return cap;
}
}
}
return null;
}
private static Collection<GenericMetadata> findAllMetadata(List<GenericMetadata> requirementsOrCapabilities, String namespace) {
List<GenericMetadata> reqsCaps = new ArrayList<ManifestHeaderProcessor.GenericMetadata>();
for (GenericMetadata reqCap : requirementsOrCapabilities) {
if (namespace.equals(reqCap.getNamespace())) {
reqsCaps.add(reqCap);
}
}
return reqsCaps;
}
public void modifiedBundle(Bundle bundle, BundleEvent event, Object registrations) {
// should really be doing something here...
}
@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(LogService.LOG_INFO, "Unregistered: " + reg);
}
}
private void log(int level, String message) {
activator.log(level, message);
}
private void log(int level, String message, Throwable th) {
activator.log(level, message, th);
}
private static GenericMetadata findRequirement(List<GenericMetadata> requirements, String namespace, String type) throws InvalidSyntaxException {
Dictionary<String, String> nsAttr = new Hashtable<String, String>();
nsAttr.put(namespace, type);
for (GenericMetadata req : requirements) {
if (namespace.equals(req.getNamespace())) {
String filterString = req.getDirectives().get(SpiFlyConstants.FILTER_DIRECTIVE);
if (filterString != null) {
Filter filter = FrameworkUtil.createFilter(filterString);
if (filter.match(nsAttr)) {
return req;
}
}
}
}
return null;
}
}