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

net.yetamine.osgi.jdbc.internal.WeavingHookService 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.util.Objects;
import java.util.OptionalLong;
import java.util.function.Function;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.hooks.weaving.WeavingHook;
import org.osgi.framework.hooks.weaving.WovenClass;
import org.osgi.framework.wiring.BundleWiring;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.yetamine.osgi.jdbc.support.WeavingSupport;
import net.yetamine.osgi.jdbc.tweak.WeavingFilter;

/**
 * Implements {@link WeavingHook} that patches calls to all significant methods
 * of {@link java.sql.DriverManager} and redirects them to the thunk class that
 * invokes the JDBC support core.
 */
final class WeavingHookService implements WeavingHook {

    private static final Logger LOGGER = LoggerFactory.getLogger(WeavingHookService.class);

    /** Re-entrant protection filter. */
    private final WeavingFilter protection;
    /** Weaving filter to apply. */
    private final WeavingFilter filter;
    /** Resolver for the caller identifiers. */
    private final Function resolver;
    /** Import entry to be added for a woven class. */
    private final String requiredImport;
    /** Re-entrancy protection flag. */
    private final ThreadLocal reentering = ThreadLocal.withInitial(() -> Boolean.FALSE);

    /**
     * Creates a new instance.
     *
     * @param bundle
     *            the parent bundle. It must not be {@code null}.
     * @param protectionFilter
     *            the protection filter to prevent re-entrant weaving. It must
     *            not be {@code null}.
     * @param customFilter
     *            the custom filter to approve weaving of a class. It must not
     *            be {@code null}.
     * @param callerResolver
     *            the resolver that provides the caller identifier to weave for
     *            a bundle. It must not be {@code null}.
     */
    public WeavingHookService(Bundle bundle, WeavingFilter protectionFilter, WeavingFilter customFilter, Function callerResolver) {
        protection = Objects.requireNonNull(protectionFilter);
        resolver = Objects.requireNonNull(callerResolver);
        filter = Objects.requireNonNull(customFilter);

        final String bundleName = bundle.getSymbolicName();
        final String bundleVersion = bundle.getVersion().toString();
        final String packageName = WeavingClassVisitor.Thunk.THUNKING_CLASS.classType().getPackage().getName();
        final String importEntry = "%s;bundle-symbolic-name=%s;bundle-version=%s"; // Should be good enough for a singleton
        requiredImport = String.format(importEntry, packageName, bundleName, bundleVersion);

        // This is important! Trigger the WeavingSupport before any weaving actually starts,
        // so that the class is loaded. The invocation moreover tests the protection filter

        WeavingSupport.execute(() -> {
            if (protection.test(bundle, WeavingSupport.class.getName())) {
                LOGGER.error("WeavingSupport test failed. This may indicate an internal error.");
            }
        });
    }

    /**
     * Creates a new instance.
     *
     * @param bundleContext
     *            the parent bundle context. It must not be {@code null}.
     * @param protectionFilter
     *            the protection filter to prevent re-entrant weaving. It must
     *            not be {@code null}.
     * @param customFilter
     *            the custom filter to approve weaving of a class. It must not
     *            be {@code null}.
     * @param callerResolver
     *            the resolver that provides the caller identifier to weave for
     *            a bundle. It must not be {@code null}.
     */
    public WeavingHookService(BundleContext bundleContext, WeavingFilter protectionFilter, WeavingFilter customFilter, Function callerResolver) {
        this(bundleContext.getBundle(), protectionFilter, customFilter, callerResolver);
    }

    /**
     * Resolves the caller identifier by the given bundle.
     *
     * 

* This method can be used as the caller resolver for this class. It just * maps the bundle identifier to the caller's. * * @param bundle * the bundle to resolve. It must not be {@code null}. * * @return the caller identifier */ public static OptionalLong defaultCallerResolver(Bundle bundle) { return OptionalLong.of(bundle.getBundleId()); } /** * @see org.osgi.framework.hooks.weaving.WeavingHook#weave(org.osgi.framework.hooks.weaving.WovenClass) */ @Override public void weave(WovenClass wovenClass) { if (reentering.get()) { WeavingSupport.execute(() -> { final String className = wovenClass.getClassName(); final Bundle bundle = wovenClass.getBundleWiring().getBundle(); LOGGER.trace("Omitted class {} from '{}': excluded due to weaving recursion.", className, bundle); }); return; } reentering.set(Boolean.TRUE); try { // Protected patching patch(wovenClass); } finally { reentering.set(Boolean.FALSE); } } /** * Patches the given class to weave if possible and necessary. * * @param wovenClass * the woven class definition. It must not be {@code null}. */ private void patch(WovenClass wovenClass) { final BundleWiring wiring = wovenClass.getBundleWiring(); final Bundle bundle = wiring.getBundle(); final String className = wovenClass.getClassName(); // Protect the service itself from recursive weaving if (!protection.test(bundle, className)) { WeavingSupport.execute(() -> { final String f = "Omitted class {} from '{}': excluded by the weaving protection filter."; LOGGER.debug(f, className, bundle); }); return; } // Run the resolver first as it should be faster than the filters final OptionalLong caller = resolver.apply(bundle); if (!caller.isPresent()) { WeavingSupport.execute(() -> { final String f = "Omitted class {} from '{}': unavailable caller identifier."; LOGGER.debug(f, className, bundle); }); return; } // Apply external filters if (!filter.test(bundle, className)) { WeavingSupport.execute(() -> { final String f = "Omitted class {} from '{}': suppressed by filters."; LOGGER.debug(f, className, bundle); }); return; } try { WeavingSupport.execute(() -> LOGGER.trace("Weaving class {} from '{}'.", className, bundle)); final ClassReader cr = new ClassReader(wovenClass.getBytes()); final ClassLoader cl = wiring.getClassLoader(); final ClassWriter cw = new WeavingClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES, cl); final WeavingClassVisitor visitor = new WeavingClassVisitor(cw, caller.getAsLong()); cr.accept(visitor, ClassReader.SKIP_FRAMES); if (!visitor.woven()) { // No need to change this class WeavingSupport.execute(() -> LOGGER.trace("Weaving class {} from '{}' cancelled.", className, bundle)); return; } wovenClass.setBytes(cw.toByteArray()); wovenClass.getDynamicImports().add(requiredImport); WeavingSupport.execute(() -> LOGGER.debug("Woven class {} from '{}'.", className, bundle)); } catch (Exception e) { WeavingSupport.execute(() -> LOGGER.warn("Failed to weave class {} from '{}'.", className, bundle, e)); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy