| /* |
| * 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.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 BundleContext context; |
| |
| private Set<Bundle> resourceBundles = Collections.synchronizedSet(new HashSet<Bundle>()); |
| |
| 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 |
| if (bundleEvent.getType() == BundleEvent.STARTED) { |
| Bundle bundle = bundleEvent.getBundle(); |
| checkAndLoadBundle(bundle); |
| } else if (bundleEvent.getType() == BundleEvent.STOPPED) { |
| Bundle bundle = bundleEvent.getBundle(); |
| 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, "UTF-8"))) { |
| String implClassName = br.readLine(); |
| while (implClassName != null) { |
| int hashIndex = implClassName.indexOf("#"); |
| if (hashIndex > 0) { |
| implClassName = implClassName.substring(0, hashIndex - 1); |
| } else if (hashIndex == 0) { |
| implClassName = ""; |
| } |
| implClassName = implClassName.trim(); |
| 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 (Exception e) { |
| LOG.log(Level.SEVERE, "Failed to load service: " + implClassName, e); |
| } catch (NoClassDefFoundError err) { |
| LOG.log(Level.SEVERE, "Failed to load service: " + implClassName, err); |
| } |
| } |
| implClassName = 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(); |
| |
| BufferedReader br = new BufferedReader(new InputStreamReader(inStream, "UTF-8")); |
| String implClassName = br.readLine(); |
| while (implClassName != null) { |
| int hashIndex = implClassName.indexOf("#"); |
| if (hashIndex > 0) { |
| implClassName = implClassName.substring(0, hashIndex - 1); |
| } else if (hashIndex == 0) { |
| implClassName = ""; |
| } |
| implClassName = implClassName.trim(); |
| 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 (Exception e) { |
| LOG.log(Level.SEVERE, "Failed to unload service: " + implClassName, e); |
| } catch (NoClassDefFoundError err) { |
| LOG.log(Level.SEVERE, "Failed to unload service: " + implClassName, err); |
| } |
| } |
| implClassName = br.readLine(); |
| } |
| br.close(); |
| } catch (RuntimeException rte) { |
| throw rte; |
| } catch (Exception e) { |
| LOG.log(Level.SEVERE, "Failed to read services from: " + entryPath, e); |
| } |
| } |
| |
| /** |
| * Service factory simply instantiating the configured service. |
| */ |
| static class JDKUtilServiceFactory implements ServiceFactory { |
| private final Class<?> serviceClass; |
| |
| public 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) { |
| } |
| } |
| } |