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

net.yetamine.osgi.jdbc.internal.DriverRegistrar 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.sql.Driver;
import java.sql.DriverAction;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;

import net.yetamine.osgi.jdbc.DriverConstants;
import net.yetamine.osgi.jdbc.support.DriverReference;

/**
 * Provides a bridge between the registration interface in the style of the
 * original {@link java.sql.DriverManager} and OSGi, so that all registered
 * drivers can be published as OSGi services as well.
 */
public final class DriverRegistrar implements BundleController {

    /* Implementation notes:
     *
     * This class is thread-safe, using 'this' as the lock. Because it is used
     * just internally, within the bundle, there is no real danger of a client
     * to interfere with such a locking policy.
     *
     * Moreover, thanks to publishing the drivers as OSGi services, it is no
     * need to be highly concurrent and intrinsic locks are good enough.
     */

    /** Drivers available via this bridging registrar. */
    private final Map drivers = new HashMap<>();
    /** Drivers published as OSGi services by the driver registration. */
    private final Map> services = new HashMap<>();
    /** Set of the identifiers of currently operational bundles. */
    private final Set operational = new HashSet<>();
    /** Bundle context for OSGi interactions. */
    private BundleContext bundleContext;

    /**
     * Creates a new instance.
     */
    DriverRegistrar() {
        // Default constructor
    }

    // Thunk support

    /**
     * Registers the given driver.
     *
     * 

* If the driver has been registered, this method does nothing. The callback * is not updated nor extended to reflect a possible change to mimic closely * {@link java.sql.DriverManager#registerDriver(Driver, DriverAction)}. * * @param driver * the driver to register. It must not be {@code null}. * @param action * the callback for the driver-related events. It must not be * {@code null}. * @param bundleId * the identifier of the registering bundle */ public void register(Driver driver, DriverAction action, long bundleId) { final DriverRegistration registration = new DriverRegistration(driver, action, bundleId); final DriverReference reference = registration.reference(); // Non-null, passing all checks synchronized (this) { if ((drivers.putIfAbsent(reference, registration) == null) && (bundleContext != null)) { publish(registration); } } } /** * Unregisters the given driver. * *

* If the given bundle identifier does not match the registering bundle or * if the driver is not registered, this method does nothing; otherwise it * unregisters the driver and invokes the callback which was provided when * the driver was registered (if any such callback exists). * * @param driver * the driver to unregister. It must not be {@code null}. * @param bundleId * the identifier of the registering bundle */ public void unregister(Driver driver, long bundleId) { final DriverReference reference = new DriverReference(driver); final DriverRegistration registration; synchronized (this) { registration = drivers.get(reference); // Here we check that the registering bundle is attempting to unregister // the driver. It might follow more DriverManager and require a permission // for the case when the call comes from a different bundle. On the other // hand, it might interfere with the service-driven approach, right? if ((registration == null) || (registration.bundleId() != bundleId)) { return; } // Remove the registration record actually final DriverRegistration removed = drivers.remove(reference); assert (removed == registration); conceal(registration); } registration.action().deregister(); // Outside of the synchronized block } // BundleController /** * @see net.yetamine.osgi.jdbc.internal.BundleController#suspend(org.osgi.framework.Bundle) */ public void suspend(Bundle bundle) { final long bundleId = bundle.getBundleId(); synchronized (this) { if (operational.remove(bundleId)) { registrations(bundleId).forEach(this::conceal); } } } /** * @see net.yetamine.osgi.jdbc.internal.BundleController#resume(org.osgi.framework.Bundle) */ public void resume(Bundle bundle) { final long bundleId = bundle.getBundleId(); synchronized (this) { if (operational.add(bundleId)) { registrations(bundleId).forEach(this::publish); } } } /** * @see net.yetamine.osgi.jdbc.internal.BundleController#cancel(org.osgi.framework.Bundle) */ public void cancel(Bundle bundle) { final long bundleId = bundle.getBundleId(); // Implicit null check // Retrieve and remove all registrations for the bundle. Save the // registrations in the list and rather process their callbacks // outside of the synchronized block. final List registrations = new ArrayList<>(); synchronized (this) { operational.remove(bundleId); for (Iterator it = drivers.values().iterator(); it.hasNext();) { final DriverRegistration registration = it.next(); if (registration.bundleId() == bundleId) { registrations.add(registration); it.remove(); // First remove conceal(registration); } } } final List exceptions = registrations.stream() // Process removed registrations outside the lock .map(DriverRegistrar::deregister) // Invoke the callback .filter(Objects::nonNull) // And collect failures .collect(Collectors.toList()); if (exceptions.isEmpty()) { // All successful, nothing to throw return; } // Make the umbrella exception to throw final String f = "Unregistration for bundle %d failed."; final RuntimeException exception = new RuntimeException(String.format(f, bundleId)); exceptions.forEach(exception::addSuppressed); throw exception; } // Activator support /** * Binds this instance to the bundle in whose context the instance should * serve. * *

* If the given context differs from the current one, the instance must * {@link #release()} in order to release from the current context, and * switch to the given context then, which means that all available drivers * shall be registered again. * * @param context * the context of the bundle to use for OSGi interactions. It * must not be {@code null}. */ public synchronized void bind(BundleContext context) { if (context.equals(bundleContext)) { // Implicit null check return; } release(); bundleContext = context; drivers.values().forEach(this::publish); } /** * Unbinds this instance from the current bundle and therefore releases all * OSGi resources related to the bundle, which means unregistering drivers, * that were registered on behalf of the bundle before. */ public synchronized void release() { drivers.values().forEach(this::conceal); assert services.isEmpty(); bundleContext = null; } // Implementation internals /** * Publishes a driver as an OSGi service on behalf of the current bundle. * *

* This method needs the common lock being held by the caller and * {@link #bundleContext} being non-{@code null}. * * @param registration * the driver registration to employ. It must not be * {@code null}. */ private void publish(DriverRegistration registration) { assert Thread.holdsLock(this); assert (bundleContext != null); if ((services.get(registration) != null) || !operational.contains(registration.bundleId())) { return; } final DriverReference reference = registration.reference(); final Dictionary properties = new Hashtable<>(); properties.put(DriverConstants.DRIVER_BUNDLE, registration.bundleId()); properties.put(DriverConstants.DRIVER_VERSION, reference.driverVersion()); properties.put(DriverConstants.DRIVER_CLASS, reference.driverClass().getTypeName()); services.put(registration, bundleContext.registerService(Driver.class, reference.driver(), properties)); } /** * Conceals the driver from the OSGi framework, i.e., unregisters the driver * service registered by {@link #publish(DriverRegistration)} before if * exists. * *

* This method needs the common lock being held by the caller, however, * {@link #bundleContext} may be {@code null} (the presence of service * registration matters). * * @param registration * the driver registration to employ. It must not be * {@code null}. */ private void conceal(DriverRegistration registration) { assert Thread.holdsLock(this); final ServiceRegistration service = services.remove(registration); if (service != null) { // Just cancel it out service.unregister(); } } /** * Returns a {@link Stream} of registrations for the given bundle. * *

* This method needs the common lock being held by the caller, however, * {@link #bundleContext} may be {@code null} (the presence of service * registration matters). * * @param bundleId * the identifier of the bundle * * @return the registrations created on behalf of the given bundle */ private Stream registrations(long bundleId) { assert Thread.holdsLock(this); return drivers.values().stream().filter(r -> r.bundleId() == bundleId); } /** * Finishes the unregistration by invoking {@link DriverAction#deregister()} * for the associated callback and returns the exception if any occurred. * * @param registration * the registration to effectively destroy. It must not be * {@code null}. * * @return the exception if any occurred, {@code null} otherwise (the * success case) */ private static Throwable deregister(DriverRegistration registration) { try { // This might be risky and fail registration.action().deregister(); return null; // Success, no exception } catch (Throwable t) { return new RuntimeException(String.format("Unregistration problem with %s.", registration), t); } } /** * Registration record of a driver. */ private static final class DriverRegistration { /** Driver reference. */ private final DriverReference reference; /** Driver action reference. */ private final DriverAction action; /** Registering bundle identifier. */ private final long bundleId; /** * Creates a new instance. * * @param driverReference * the driver reference. It must not be {@code null}. * @param driverAction * the action to use. It must not be {@code null}. * @param registeringBundleId * the identifier of the registering bundle */ public DriverRegistration(DriverReference driverReference, DriverAction driverAction, long registeringBundleId) { reference = Objects.requireNonNull(driverReference); action = Objects.requireNonNull(driverAction); bundleId = registeringBundleId; } /** * Creates a new instance. * * @param driver * the driver instance. It must not be {@code null}. * @param driverAction * the action to use. It must not be {@code null}. * @param registeringBundleId * the identifier of the registering bundle */ public DriverRegistration(Driver driver, DriverAction driverAction, long registeringBundleId) { this(new DriverReference(driver), driverAction, registeringBundleId); } /** * @see java.lang.Object#toString() */ @Override public String toString() { return reference.toString(); } /** * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof DriverRegistration) { final DriverRegistration o = (DriverRegistration) obj; return (bundleId == o.bundleId) && reference.equals(o.reference); } return false; } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return Objects.hash(bundleId, reference); } /** * Returns the driver reference. * * @return the driver reference */ public DriverReference reference() { return reference; } /** * Returns the action of the driver. * * @return the action of the driver, never {@code null} */ public DriverAction action() { return action; } /** * Returns the registering bundle identifier. * * @return the registering bundle identifier */ public long bundleId() { return bundleId; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy