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

org.glassfish.jersey.internal.OsgiRegistry Maven / Gradle / Ivy

Go to download

A bundle project producing JAX-RS RI bundles. The primary artifact is an "all-in-one" OSGi-fied JAX-RS RI bundle (jaxrs-ri.jar). Attached to that are two compressed JAX-RS RI archives. The first archive (jaxrs-ri.zip) consists of binary RI bits and contains the API jar (under "api" directory), RI libraries (under "lib" directory) as well as all external RI dependencies (under "ext" directory). The secondary archive (jaxrs-ri-src.zip) contains buildable JAX-RS RI source bundle and contains the API jar (under "api" directory), RI sources (under "src" directory) as well as all external RI dependencies (under "ext" directory). The second archive also contains "build.xml" ANT script that builds the RI sources. To build the JAX-RS RI simply unzip the archive, cd to the created jaxrs-ri directory and invoke "ant" from the command line.

There is a newer version: 3.1.6
Show newest version
/*
 * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.internal;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

import jakarta.ws.rs.ProcessingException;

import org.glassfish.jersey.internal.util.ReflectionHelper;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleReference;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.SynchronousBundleListener;

/**
 * Utility class to deal with OSGi runtime specific behavior.
 * This is mainly to handle META-INF/services lookup
 * and generic/application class lookup issue in OSGi.
 *
 * When OSGi runtime is detected by the {@link ServiceFinder} class,
 * an instance of OsgiRegistry is created and associated with given
 * OSGi BundleContext. META-INF/services entries are then being accessed
 * via the OSGi Bundle API as direct ClassLoader#getResource() method invocation
 * does not work in this case within OSGi.
 *
 * @author Jakub Podlesak
 * @author Adam Lindenthal
 */
public final class OsgiRegistry implements SynchronousBundleListener {

    private static final String WEB_INF_CLASSES = "WEB-INF/classes/";
    private static final String CoreBundleSymbolicNAME = "org.glassfish.jersey.core.jersey-common";
    private static final Logger LOGGER = Logger.getLogger(OsgiRegistry.class.getName());

    private final BundleContext bundleContext;
    private final Map>>>> factories =
            new HashMap>>>>();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    private static OsgiRegistry instance;

    private final Map classToBundleMapping = new HashMap();

    /**
     * Returns an {@code OsgiRegistry} instance. Call this method only if sure that the application is running in OSGi
     * environment, otherwise a call to this method can lead to an {@link ClassNotFoundException}.
     *
     * @return an {@code OsgiRegistry} instance.
     */
    public static synchronized OsgiRegistry getInstance() {
        if (instance == null) {
            final ClassLoader classLoader = AccessController
                    .doPrivileged(ReflectionHelper.getClassLoaderPA(ReflectionHelper.class));
            if (classLoader instanceof BundleReference) {
                final BundleContext context = FrameworkUtil.getBundle(OsgiRegistry.class).getBundleContext();
                if (context != null) { // context could be still null if the current bundle has not been started
                    instance = new OsgiRegistry(context);
                }
            }
        }
        return instance;
    }

    private final class OsgiServiceFinder extends ServiceFinder.ServiceIteratorProvider {

        final ServiceFinder.ServiceIteratorProvider defaultIterator = new ServiceFinder.DefaultServiceIteratorProvider();

        @Override
        public  Iterator createIterator(
                final Class serviceClass,
                final String serviceName,
                final ClassLoader loader,
                final boolean ignoreOnClassNotFound) {

            final List> providerClasses = locateAllProviders(serviceName);
            if (!providerClasses.isEmpty()) {
                return new Iterator() {

                    Iterator> it = providerClasses.iterator();

                    @Override
                    public boolean hasNext() {
                        return it.hasNext();
                    }

                    @SuppressWarnings("unchecked")
                    @Override
                    public T next() {
                        final Class nextClass = (Class) it.next();
                        try {
                            return nextClass.newInstance();
                        } catch (final Exception ex) {
                            final ServiceConfigurationError sce = new ServiceConfigurationError(serviceName + ": "
                                    + LocalizationMessages.PROVIDER_COULD_NOT_BE_CREATED(
                                    nextClass.getName(), serviceClass, ex.getLocalizedMessage()));
                            sce.initCause(ex);
                            throw sce;
                        }
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }
            return defaultIterator.createIterator(serviceClass, serviceName, loader, ignoreOnClassNotFound);
        }

        @Override
        public  Iterator> createClassIterator(
                final Class service, final String serviceName, final ClassLoader loader, final boolean ignoreOnClassNotFound) {
            final List> providerClasses = locateAllProviders(serviceName);
            if (!providerClasses.isEmpty()) {
                return new Iterator>() {

                    Iterator> it = providerClasses.iterator();

                    @Override
                    public boolean hasNext() {
                        return it.hasNext();
                    }

                    @SuppressWarnings("unchecked")
                    @Override
                    public Class next() {
                        return (Class) it.next();
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }
            return defaultIterator.createClassIterator(service, serviceName, loader, ignoreOnClassNotFound);
        }
    }

    private static class BundleSpiProvidersLoader implements Callable>> {

        private final String spi;
        private final URL spiRegistryUrl;
        private final String spiRegistryUrlString;
        private final Bundle bundle;

        BundleSpiProvidersLoader(final String spi, final URL spiRegistryUrl, final Bundle bundle) {
            this.spi = spi;
            this.spiRegistryUrl = spiRegistryUrl;
            this.spiRegistryUrlString = spiRegistryUrl.toExternalForm();
            this.bundle = bundle;
        }

        @Override
        public List> call() throws Exception {
            BufferedReader reader = null;

            try {
                if (LOGGER.isLoggable(Level.FINEST)) {
                    LOGGER.log(Level.FINEST, "Loading providers for SPI: {0}", spi);
                }
                reader = new BufferedReader(new InputStreamReader(spiRegistryUrl.openStream(), "UTF-8"));
                String providerClassName;

                final List> providerClasses = new ArrayList>();
                while ((providerClassName = reader.readLine()) != null) {
                    if (providerClassName.trim().length() == 0) {
                        continue;
                    }
                    if (providerClassName.startsWith("#")) {
                        continue;
                    }
                    if (LOGGER.isLoggable(Level.FINEST)) {
                        LOGGER.log(Level.FINEST, "SPI provider: {0}", providerClassName);
                    }
                    providerClasses.add(loadClass(bundle, providerClassName));
                }

                return providerClasses;
            } catch (final Exception e) {
                LOGGER.log(Level.WARNING, LocalizationMessages.EXCEPTION_CAUGHT_WHILE_LOADING_SPI_PROVIDERS(), e);
                throw e;
            } catch (final Error e) {
                LOGGER.log(Level.WARNING, LocalizationMessages.ERROR_CAUGHT_WHILE_LOADING_SPI_PROVIDERS(), e);
                throw e;
            } finally {
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (final IOException ioe) {
                        LOGGER.log(Level.FINE, "Error closing SPI registry stream:" + spiRegistryUrl, ioe);
                    }
                }
            }
        }

        @Override
        public String toString() {
            return spiRegistryUrlString;
        }

        @Override
        public int hashCode() {
            return spiRegistryUrlString.hashCode();
        }

        @Override
        public boolean equals(final Object obj) {
            if (obj instanceof BundleSpiProvidersLoader) {
                return spiRegistryUrlString.equals(((BundleSpiProvidersLoader) obj).spiRegistryUrlString);
            } else {
                return false;
            }
        }
    }

    @Override
    public void bundleChanged(final BundleEvent event) {

        if (event.getType() == BundleEvent.RESOLVED) {
            register(event.getBundle());
        } else if (event.getType() == BundleEvent.UNRESOLVED || event.getType() == BundleEvent.UNINSTALLED) {

            final Bundle unregisteredBundle = event.getBundle();

            lock.writeLock().lock();
            try {
                factories.remove(unregisteredBundle.getBundleId());

                if (unregisteredBundle.getSymbolicName().equals(CoreBundleSymbolicNAME)) {
                    bundleContext.removeBundleListener(this);
                    factories.clear();
                }
            } finally {
                lock.writeLock().unlock();
            }
        }
    }

    /**
     * Translates bundle entry path as returned from {@link org.osgi.framework.Bundle#findEntries(String, String, boolean)} to
     * fully qualified class name that resides in given package path (directly or indirectly in its subpackages).
     *
     * @param packagePath     The package path where the class is located (even recursively)
     * @param bundleEntryPath The bundle path to translate.
     * @return Fully qualified class name.
     */
    public static String bundleEntryPathToClassName(String packagePath, String bundleEntryPath) {
        // normalize packagePath
        packagePath = normalizedPackagePath(packagePath);

        // remove WEB-INF/classes from bundle entry path
        if (bundleEntryPath.contains(WEB_INF_CLASSES)) {
            bundleEntryPath = bundleEntryPath.substring(bundleEntryPath.indexOf(WEB_INF_CLASSES) + WEB_INF_CLASSES.length());
        }

        final int packageIndex = bundleEntryPath.indexOf(packagePath);

        String normalizedClassNamePath = packageIndex > -1
                // the package path was found in the bundle path
                ? bundleEntryPath.substring(packageIndex)
                // the package path is not included in the bundle entry path
                // fall back to the original implementation of the translation which does not consider recursion
                : packagePath + bundleEntryPath.substring(bundleEntryPath.lastIndexOf('/') + 1);

        return (normalizedClassNamePath.startsWith("/") ? normalizedClassNamePath.substring(1) : normalizedClassNamePath)
                .replace(".class", "").replace('/', '.');
    }

    /**
     * Returns whether the given entry path is located directly in the provided package path. That is,
     * if the entry is located in a sub-package, then {@code false} is returned.
     *
     * @param packagePath Package path which the entry is compared to
     * @param entryPath Entry path
     * @return Whether the given entry path is located directly in the provided package path.
     */
    public static boolean isPackageLevelEntry(String packagePath, final String entryPath) {
        // normalize packagePath
        packagePath = normalizedPackagePath(packagePath);

        // if the package path is contained in the jar entry name, subtract it
        String entryWithoutPackagePath = entryPath.contains(packagePath)
                ? entryPath.substring(entryPath.indexOf(packagePath) + packagePath.length())
                : entryPath;

        return !(entryWithoutPackagePath.startsWith("/") ? entryWithoutPackagePath.substring(1)
                         : entryWithoutPackagePath)
                .contains("/");
    }

    /**
     * Normalized package returns path that does not start with '/' character and ends with '/' character.
     * If the argument is '/' then returned value is empty string "".
     *
     * @param packagePath package path to normalize.
     * @return Normalized package path.
     */
    public static String normalizedPackagePath(String packagePath) {
        packagePath = packagePath.startsWith("/") ? packagePath.substring(1) : packagePath;
        packagePath = packagePath.endsWith("/") ? packagePath : packagePath + "/";
        packagePath = "/".equals(packagePath) ? "" : packagePath;
        return packagePath;
    }

    /**
     * Get URLs of resources from a given package.
     *
     * @param packagePath package.
     * @param classLoader resource class loader.
     * @param recursive   whether the given package path should be scanned recursively by OSGi
     * @return URLs of the located resources.
     */
    @SuppressWarnings("unchecked")
    public Enumeration getPackageResources(final String packagePath,
                                                final ClassLoader classLoader,
                                                final boolean recursive) {
        final List result = new LinkedList();

        for (final Bundle bundle : bundleContext.getBundles()) {
            // Look for resources at the given  and at WEB-INF/classes/ in case a WAR is being examined.
            for (final String bundlePackagePath : new String[] {packagePath, WEB_INF_CLASSES + packagePath}) {
                final Enumeration enumeration = findEntries(bundle, bundlePackagePath, "*.class", recursive);

                if (enumeration != null) {
                    while (enumeration.hasMoreElements()) {
                        final URL url = enumeration.nextElement();
                        final String path = url.getPath();

                        classToBundleMapping.put(bundleEntryPathToClassName(packagePath, path), bundle);
                        result.add(url);
                    }
                }
            }

            // Now interested only in .jar provided by current bundle.
            final Enumeration jars = findEntries(bundle, "/", "*.jar", true);
            if (jars != null) {
                while (jars.hasMoreElements()) {
                    final URL jar = jars.nextElement();
                    final InputStream inputStream = classLoader.getResourceAsStream(jar.getPath());
                    if (inputStream == null) {
                        LOGGER.config(LocalizationMessages.OSGI_REGISTRY_ERROR_OPENING_RESOURCE_STREAM(jar));
                        continue;
                    }
                    final JarInputStream jarInputStream;
                    try {
                        jarInputStream = new JarInputStream(inputStream);
                    } catch (final IOException ex) {
                        LOGGER.log(Level.CONFIG, LocalizationMessages.OSGI_REGISTRY_ERROR_PROCESSING_RESOURCE_STREAM(jar), ex);
                        try {
                            inputStream.close();
                        } catch (final IOException e) {
                            // ignored
                        }
                        continue;
                    }

                    try {
                        JarEntry jarEntry;
                        while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
                            final String jarEntryName = jarEntry.getName();
                            final String jarEntryNameLeadingSlash = jarEntryName.startsWith("/")
                                    ? jarEntryName : "/" + jarEntryName;

                            if (jarEntryName.endsWith(".class")
                                    // Added leading and trailing slashes '/' to package path (e.g. '/com/') helps us to not
                                    // accidentally match sub-strings of the package path (e.g., if package path 'com' was used
                                    // for scanning, package 'whatever.foo.telecom' would be matched because of word 'tele[com]').
                                    // Note that we cannot avoid all corner cases with accidental matches since jar
                                    // entry name might be almost anything (e.g., if package path 'telecom' was used, package
                                    // 'whatever.foo.telecom' will be matched and there is no way to avoid it unless user
                                    // explicitly instructs us to do so somehow (not implemented)
                                    && jarEntryNameLeadingSlash.contains("/" + normalizedPackagePath(packagePath))) {
                                if (!recursive && !isPackageLevelEntry(packagePath, jarEntryName)) {
                                    continue;
                                }
                                classToBundleMapping.put(jarEntryName.replace(".class", "").replace('/', '.'), bundle);
                                result.add(bundle.getResource(jarEntryName));
                            }
                        }
                    } catch (final Exception ex) {
                        LOGGER.log(Level.CONFIG, LocalizationMessages.OSGI_REGISTRY_ERROR_PROCESSING_RESOURCE_STREAM(jar), ex);
                    } finally {
                        try {
                            jarInputStream.close();
                        } catch (final IOException e) {
                            // ignored
                        }
                    }
                }
            }
        }

        return Collections.enumeration(result);
    }

    /**
     * Get the Class from the class name.
     * 

* The context class loader will be utilized if accessible and non-null. * Otherwise the defining class loader of this class will * be utilized. * * @param className the class name. * @return the Class, otherwise null if the class cannot be found. * @throws ClassNotFoundException if the class cannot be found. */ public Class classForNameWithException(final String className) throws ClassNotFoundException { final Bundle bundle = classToBundleMapping.get(className); if (bundle == null) { throw new ClassNotFoundException(className); } return loadClass(bundle, className); } /** * Tries to load resource bundle via OSGi means. No caching involved here, * as localization properties are being cached in Localizer class already. * * @param bundleName name of the resource bundle to load * @return resource bundle instance if found, null otherwise */ public ResourceBundle getResourceBundle(final String bundleName) { final int lastDotIndex = bundleName.lastIndexOf('.'); final String path = bundleName.substring(0, lastDotIndex).replace('.', '/'); final String propertiesName = bundleName.substring(lastDotIndex + 1, bundleName.length()) + ".properties"; for (final Bundle bundle : bundleContext.getBundles()) { final Enumeration entries = findEntries(bundle, path, propertiesName, false); if (entries != null && entries.hasMoreElements()) { final URL entryUrl = entries.nextElement(); try { return new PropertyResourceBundle(entryUrl.openStream()); } catch (final IOException ex) { if (LOGGER.isLoggable(Level.FINE)) { // does not make sense to localize this LOGGER.fine("Exception caught when tried to load resource bundle in OSGi"); } return null; } } } return null; } /** * Creates a new OsgiRegistry instance bound to a particular OSGi runtime. * The only parameter must be an instance of a {@link BundleContext}. * * @param bundleContext must be a non-null instance of a BundleContext */ private OsgiRegistry(final BundleContext bundleContext) { this.bundleContext = bundleContext; } /** * Will hook up this instance with the OSGi runtime. * This is to actually update SPI provider lookup and class loading mechanisms in Jersey * to utilize OSGi features. */ void hookUp() { setOSGiServiceFinderIteratorProvider(); bundleContext.addBundleListener(this); registerExistingBundles(); } private void registerExistingBundles() { for (final Bundle bundle : bundleContext.getBundles()) { if (bundle.getState() == Bundle.RESOLVED || bundle.getState() == Bundle.STARTING || bundle.getState() == Bundle.ACTIVE || bundle.getState() == Bundle.STOPPING) { register(bundle); } } } private void setOSGiServiceFinderIteratorProvider() { ServiceFinder.setIteratorProvider(new OsgiServiceFinder()); } private void register(final Bundle bundle) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.log(Level.FINEST, "checking bundle {0}", bundle.getBundleId()); } Map>>> map; lock.writeLock().lock(); try { map = factories.get(bundle.getBundleId()); if (map == null) { map = new ConcurrentHashMap>>>(); factories.put(bundle.getBundleId(), map); } } finally { lock.writeLock().unlock(); } final Enumeration e = findEntries(bundle, "META-INF/services/", "*", false); if (e != null) { while (e.hasMoreElements()) { final URL u = e.nextElement(); final String url = u.toString(); if (url.endsWith("/")) { continue; } final String factoryId = url.substring(url.lastIndexOf("/") + 1); map.put(factoryId, new BundleSpiProvidersLoader(factoryId, u, bundle)); } } } private List> locateAllProviders(final String serviceName) { lock.readLock().lock(); try { final List> result = new LinkedList>(); for (final Map>>> value : factories.values()) { if (value.containsKey(serviceName)) { try { result.addAll(value.get(serviceName).call()); } catch (final Exception ex) { // ignore } } } return result; } finally { lock.readLock().unlock(); } } private static Class loadClass(final Bundle bundle, final String className) throws ClassNotFoundException { try { return AccessController.doPrivileged(new PrivilegedExceptionAction>() { @Override public Class run() throws ClassNotFoundException { return bundle.loadClass(className); } }); } catch (final PrivilegedActionException ex) { final Exception originalException = ex.getException(); if (originalException instanceof ClassNotFoundException) { throw (ClassNotFoundException) originalException; } else if (originalException instanceof RuntimeException) { throw (RuntimeException) originalException; } else { throw new ProcessingException(originalException); } } } private static Enumeration findEntries(final Bundle bundle, final String path, final String fileNamePattern, final boolean recursive) { return AccessController.doPrivileged(new PrivilegedAction>() { @SuppressWarnings("unchecked") @Override public Enumeration run() { return bundle.findEntries(path, fileNamePattern, recursive); } }); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy