blob: 667a148f145522985fc1c845873552444730d25c [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.tamaya.core.internal;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.tamaya.spisupport.PriorityServiceComparator;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
/**
* A bundle listener that registers services defined in META-INF/services, when
* a bundle is starting.
*
* @author anatole@apache.org
*/
@SuppressWarnings("rawtypes")
public class OSGIServiceLoader implements BundleListener {
// Provide logging
private static final Logger LOG = Logger.getLogger(OSGIServiceLoader.class.getName());
private static final String META_INF_SERVICES = "META-INF/services/";
private final BundleContext context;
private final Set<Bundle> resourceBundles = Collections.synchronizedSet(new HashSet<>());
public OSGIServiceLoader(BundleContext context) {
this.context = Objects.requireNonNull(context);
// Check for matching bundles already installed...
for (Bundle bundle : context.getBundles()) {
if (bundle.getState() == Bundle.ACTIVE) {
checkAndLoadBundle(bundle);
}
}
}
public BundleContext getBundleContext() {
return context;
}
public Set<Bundle> getResourceBundles() {
synchronized (resourceBundles) {
return new HashSet<>(resourceBundles);
}
}
@Override
public void bundleChanged(BundleEvent bundleEvent) {
// Parse and createObject metadata when installed
Bundle bundle = bundleEvent.getBundle();
if (bundleEvent.getType() == BundleEvent.STARTED) {
checkAndLoadBundle(bundle);
} else if (bundleEvent.getType() == BundleEvent.STOPPED) {
checkAndUnloadBundle(bundle);
}
}
private void checkAndUnloadBundle(Bundle bundle) {
if (bundle.getEntry(META_INF_SERVICES) == null) {
return;
}
synchronized (resourceBundles) {
resourceBundles.remove(bundle);
LOG.fine("Unregistered ServiceLoader bundle: " + bundle.getSymbolicName());
}
Enumeration<String> entryPaths = bundle.getEntryPaths(META_INF_SERVICES);
while (entryPaths.hasMoreElements()) {
String entryPath = entryPaths.nextElement();
if (!entryPath.endsWith("/")) {
removeEntryPath(bundle, entryPath);
}
}
}
private void checkAndLoadBundle(Bundle bundle) {
if (bundle.getEntry(META_INF_SERVICES) == null) {
return;
}
synchronized (resourceBundles) {
resourceBundles.add(bundle);
LOG.info("Registered ServiceLoader bundle: " + bundle.getSymbolicName());
}
Enumeration<String> entryPaths = bundle.getEntryPaths(META_INF_SERVICES);
while (entryPaths.hasMoreElements()) {
String entryPath = entryPaths.nextElement();
if (!entryPath.endsWith("/")) {
processEntryPath(bundle, entryPath);
}
}
}
private void processEntryPath(Bundle bundle, String entryPath) {
try {
String serviceName = entryPath.substring(META_INF_SERVICES.length());
if (!serviceName.startsWith("org.apache.tamaya")) {
// Ignore non Tamaya entries...
return;
}
Class<?> serviceClass = bundle.loadClass(serviceName);
URL child = bundle.getEntry(entryPath);
InputStream inStream = child.openStream();
LOG.info("Loading Services " + serviceClass.getName() + " from bundle...: " + bundle.getSymbolicName());
try (BufferedReader br = new BufferedReader(new InputStreamReader(inStream, StandardCharsets.UTF_8))) {
String line = br.readLine();
while (line != null) {
String implClassName = getImplClassName(line);
if (implClassName.length() > 0) {
try {
// Load the service class
LOG.fine("Loading Class " + implClassName + " from bundle...: " + bundle.getSymbolicName());
Class<?> implClass = bundle.loadClass(implClassName);
if (!serviceClass.isAssignableFrom(implClass)) {
LOG.warning("Configured service: " + implClassName + " is not assignable to "
+ serviceClass.getName());
continue;
}
LOG.info("Loaded Service Factory (" + serviceName + "): " + implClassName);
// Provide service properties
Hashtable<String, String> props = new Hashtable<>();
props.put(Constants.VERSION_ATTRIBUTE, bundle.getVersion().toString());
String vendor = bundle.getHeaders().get(Constants.BUNDLE_VENDOR);
props.put(Constants.SERVICE_VENDOR, (vendor != null ? vendor : "anonymous"));
// Translate annotated @Priority into a service ranking
props.put(Constants.SERVICE_RANKING,
String.valueOf(PriorityServiceComparator.getPriority(implClass)));
// Register the service factory on behalf of the intercepted bundle
JDKUtilServiceFactory factory = new JDKUtilServiceFactory(implClass);
BundleContext bundleContext = bundle.getBundleContext();
bundleContext.registerService(serviceName, factory, props);
LOG.info("Registered Tamaya service class: " + implClassName + "(" + serviceName + ")");
} catch (NoClassDefFoundError | Exception err) {
LOG.log(Level.SEVERE, "Failed to load service: " + implClassName, err);
}
}
line = br.readLine();
}
}
} catch (RuntimeException rte) {
throw rte;
} catch (Exception e) {
LOG.log(Level.SEVERE, "Failed to read services from: " + entryPath, e);
}
}
private void removeEntryPath(Bundle bundle, String entryPath) {
try {
String serviceName = entryPath.substring(META_INF_SERVICES.length());
if (!serviceName.startsWith("org.apache.tamaya")) {
// Ignore non Tamaya entries...
return;
}
Class<?> serviceClass = bundle.loadClass(serviceName);
URL child = bundle.getEntry(entryPath);
InputStream inStream = child.openStream();
try (BufferedReader br = new BufferedReader(new InputStreamReader(inStream, StandardCharsets.UTF_8))) {
String line = br.readLine();
while (line != null) {
String implClassName = getImplClassName(line);
if (implClassName.length() > 0) {
LOG.fine("Unloading Service (" + serviceName + "): " + implClassName);
try {
// Load the service class
Class<?> implClass = bundle.loadClass(implClassName);
if (!serviceClass.isAssignableFrom(implClass)) {
LOG.warning("Configured service: " + implClassName + " is not assignable to "
+ serviceClass.getName());
continue;
}
ServiceReference<?> ref = bundle.getBundleContext().getServiceReference(implClass);
if (ref != null) {
bundle.getBundleContext().ungetService(ref);
}
} catch (NoClassDefFoundError | Exception err) {
LOG.log(Level.SEVERE, "Failed to unload service: " + implClassName, err);
}
}
line = br.readLine();
}
}
} catch (RuntimeException rte) {
throw rte;
} catch (Exception e) {
LOG.log(Level.SEVERE, "Failed to read services from: " + entryPath, e);
}
}
private String getImplClassName(String line) {
int hashIndex = line.indexOf("#");
if (hashIndex > 0) {
return line.substring(0, hashIndex - 1).trim();
} else if (hashIndex == 0) {
return "";
} else {
return line.trim();
}
}
/**
* Service factory simply instantiating the configured service.
*/
static class JDKUtilServiceFactory implements ServiceFactory {
private final Class<?> serviceClass;
JDKUtilServiceFactory(Class<?> serviceClass) {
this.serviceClass = serviceClass;
}
@Override
public Object getService(Bundle bundle, ServiceRegistration registration) {
try {
LOG.fine("Creating Service...:" + serviceClass.getName());
return serviceClass.getConstructor().newInstance();
} catch (Exception ex) {
throw new IllegalStateException("Failed to createObject service: " + serviceClass.getName(), ex);
}
}
@Override
public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) {
}
}
}