All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.yetamine.osgi.jdbc.internal.BundleExtender Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2016 Yetamine
 *
 * Licensed 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 net.yetamine.osgi.jdbc.internal;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.sql.Driver;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.util.tracker.BundleTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.yetamine.osgi.jdbc.tweak.BundleControl;

/**
 * Implements the extender that monitors bundle loading and triggers loading
 * {@link Driver} implementations published using the {@link ServiceLoader}
 * extensions.
 *
 * 

* A driver shall be registered by loading its service class; the class should * register the driver itself, which unfortunately does not allow reloading an * unregistered driver. Therefore all registered drivers are kept registered as * long as their registering bundle remains resolved. */ final class BundleExtender implements AutoCloseable { static final Logger LOGGER = LoggerFactory.getLogger(BundleExtender.class); /** Control tracker to provide a unified control. */ private final BundleControlTracker controlTracker; /** Bundle tracker (the core of the extender). */ private final Tracker bundleTracker; /** * Creates a new instance. * * @param bundleContext * the bundle context for which this service should run. It must * not be {@code null}. * @param bundleController * the bundle controller to manage bundle states. It must not be * {@code null}. */ public BundleExtender(BundleContext bundleContext, BundleController bundleController) { controlTracker = new BundleControlTracker(bundleContext); bundleTracker = new Tracker(bundleContext, bundleController, controlTracker); } /** * Makes this instance operational if not operational yet. */ public synchronized void open() { LOGGER.debug("Opening extender."); controlTracker.open(); bundleTracker.open(); } /** * Closes all operations related to this instance, putting it out of * service. * * @see java.lang.AutoCloseable#close() */ public synchronized void close() { LOGGER.debug("Closing extender."); bundleTracker.close(); controlTracker.close(); } /** * A tracker implementing the core of the extender. */ private static final class Tracker extends BundleTracker { /** Controller for resuming and suspending bundles. */ private final BundleController controller; /** Control for querying bundle options. */ private final BundleControl control; /** * Creates a new instance. * * @param bundleContext * the context of this bundle. It must not be {@code null}. * @param bundleController * the controller for resuming and suspending bundles. It * must not be {@code null}. * @param bundleControl * the control for querying bundle options. It must not be * {@code null}. */ public Tracker(BundleContext bundleContext, BundleController bundleController, BundleControl bundleControl) { super(bundleContext, ~(Bundle.INSTALLED | Bundle.UNINSTALLED), null); controller = Objects.requireNonNull(bundleController); control = Objects.requireNonNull(bundleControl); } /** * @see org.osgi.util.tracker.BundleTracker#addingBundle(org.osgi.framework.Bundle, * org.osgi.framework.BundleEvent) */ @Override public Context addingBundle(Bundle bundle, BundleEvent event) { final Set drivers = discover(bundle); final BundleControl.Options options = new BundleControlOptions(bundle, drivers); control.adjust(options); // Adjust the options with the bundle control services final Set loadable = options.loadableDrivers().stream() // Make a clean copy .filter(drivers::contains) // Limit to the subset .collect(Collectors.collectingAndThen( // Make an optimized set Collectors.toCollection(HashSet::new), // Make a mutable set c -> c.isEmpty() ? Collections.emptySet() : c) // But replace it with the empty singleton if empty ); final BundleControl.Condition condition = options.driversAvailable(); final Context result = new Context(controller, bundle, loadable, condition); result.update(); return result; } /** * @see org.osgi.util.tracker.BundleTracker#modifiedBundle(org.osgi.framework.Bundle, * org.osgi.framework.BundleEvent, java.lang.Object) */ @Override public void modifiedBundle(Bundle bundle, BundleEvent event, Context extenderContext) { extenderContext.update(); } /** * @see org.osgi.util.tracker.BundleTracker#removedBundle(org.osgi.framework.Bundle, * org.osgi.framework.BundleEvent, java.lang.Object) */ @Override public void removedBundle(Bundle bundle, BundleEvent event, Context extenderContext) { controller.cancel(bundle); } /** * Discovers drivers published by a bundle. * * @param bundle * the bundle to inspect. It must not be {@code null}. * * @return a set of found driver names; the result may be an immutable * instance if empty, otherwise it must be a mutable instance */ private static Set discover(Bundle bundle) { LOGGER.debug("Discovering drivers from '{}'.", bundle); final URL url = AccessController.doPrivileged((PrivilegedAction) () -> { return bundle.getResource("/META-INF/services/java.sql.Driver"); }); if (url == null) { // Missing the descriptor completely LOGGER.debug("No drivers declared in '{}'.", bundle); return Collections.emptySet(); } final Set result = new HashSet<>(); final Charset cs = StandardCharsets.UTF_8; try (BufferedReader r = new BufferedReader(new InputStreamReader(url.openStream(), cs))) { r.lines().map(String::trim).forEach(result::add); } catch (IOException e) { LOGGER.warn("Failed to load the driver list from '{}'.", bundle, e); } final int size = result.size(); if (size > 0) { // There are some drivers to return after all, return the mutable set LOGGER.debug("Discovered {} driver(s) in '{}': {}", size, bundle, result); return result; } LOGGER.debug("Discovered no drivers in '{}'.", bundle); return Collections.emptySet(); } } /** * Represents the bundle extender context. */ private static final class Context implements AutoCloseable { /** Extended bundle. */ private final Bundle bundle; /** Bundle controller to use. */ private final BundleController controller; /** List of drivers awaiting the condition. */ private final Collection pending; /** Condition for triggering the update. */ private final Predicate condition; /** * Creates a new instance. * * @param bundleController * the bundle controller to use for managing the bundle's * drivers. It must not be {@code null}. * @param extendedBundle * the bundle to extend. It must not be {@code null}. * @param driverNames * the set of driver names. It must be an instance that * allows shrinking (removing elements) until empty and * possibly even immutable if empty. The caller thereby * passes the ownership to the new instance (when the * instance is mutable). * @param extenderCondition * the condition for registering drivers as services. It must * not be {@code null}. */ public Context(BundleController bundleController, Bundle extendedBundle, Set driverNames, Predicate extenderCondition) { controller = Objects.requireNonNull(bundleController); condition = Objects.requireNonNull(extenderCondition); bundle = Objects.requireNonNull(extendedBundle); pending = Objects.requireNonNull(driverNames); } /** * Updates the context by triggering load of remaining drivers. */ public void update() { if (!condition.test(bundle)) { controller.suspend(bundle); return; } controller.resume(bundle); for (Iterator it = pending.iterator(); it.hasNext();) { final String driver = it.next(); try { // Loading the driver if (load(driver) != null) { it.remove(); } } catch (Exception e) { LOGGER.warn("Loading driver '{}' from '{}' failed.", driver, bundle, e); } } } /** * @see java.lang.AutoCloseable#close() */ public void close() { controller.cancel(bundle); } /** * Loads a driver service class. * * @param driver * the driver name. It must be a valid driver service class * name. * * @return the service class, or {@code null} if wiring to the bundle * could not be retrieved * * @throws Exception * if the wiring exists, but the class loading failed */ private Class load(String driver) throws Exception { return AccessController.doPrivileged((PrivilegedExceptionAction>) () -> { final BundleWiring wiring = bundle.adapt(BundleWiring.class); if (wiring == null) { // Maybe a fragment, or simply not allowed to get the wiring LOGGER.warn("Skipped driver '{}' from '{}' due to missing wiring.", driver, bundle); return null; } final Class result = bundle.loadClass(driver); Class.forName(result.getName(), true, result.getClassLoader()); LOGGER.info("Loaded driver '{}' from '{}'.", driver, bundle); return result; }); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy