org.glassfish.jersey.internal.OsgiRegistry Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jersey-common Show documentation
Show all versions of jersey-common Show documentation
Jersey core common packages
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2012 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.jersey.internal;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
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.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleReference;
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 (jakub.podlesak at oracle.com)
*/
final class OsgiRegistry implements SynchronousBundleListener {
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 final class OsgiServiceFinder extends ServiceFinder.ServiceIteratorProvider {
final ServiceFinder.ServiceIteratorProvider defaultIterator = new ServiceFinder.DefaultServiceIteratorProvider();
@Override
public Iterator createIterator(final Class serviceClass, final String serviceName, ClassLoader loader, boolean ignoreOnClassNotFound) {
final List> providerClasses = locateAllProviders(serviceName);
if (!providerClasses.isEmpty()) {
return new Iterator() {
Iterator> it = providerClasses.iterator();
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public T next() {
Class nextClass = (Class) it.next();
try {
return nextClass.newInstance();
} catch (Exception ex) {
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(Class service, String serviceName, ClassLoader loader, 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 Bundle bundle;
BundleSpiProvidersLoader(final String spi, final URL spiRegistryUrl, final Bundle bundle) {
this.spi = spi;
this.spiRegistryUrl = spiRegistryUrl;
this.bundle = bundle;
}
@Override
public List> call() throws Exception {
try {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST, "Loading providers for SPI: {0}", spi);
}
final BufferedReader br = new BufferedReader(new InputStreamReader(spiRegistryUrl.openStream(), "UTF-8"));
String providerClassName;
final List> providerClasses = new ArrayList>();
while ((providerClassName = br.readLine()) != null) {
if (providerClassName.trim().length() == 0) {
continue;
}
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST, "SPI provider: {0}", providerClassName);
}
providerClasses.add(bundle.loadClass(providerClassName));
}
br.close();
return providerClasses;
} catch (Exception e) {
LOGGER.log(Level.WARNING, LocalizationMessages.EXCEPTION_CAUGHT_WHILE_LOADING_SPI_PROVIDERS(), e);
throw e;
} catch (Error e) {
LOGGER.log(Level.WARNING, LocalizationMessages.ERROR_CAUGHT_WHILE_LOADING_SPI_PROVIDERS(), e);
throw e;
}
}
@Override
public String toString() {
return spiRegistryUrl.toString();
}
@Override
public int hashCode() {
return spiRegistryUrl.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof BundleSpiProvidersLoader) {
return spiRegistryUrl.equals(((BundleSpiProvidersLoader) obj).spiRegistryUrl);
} else {
return false;
}
}
}
@Override
public void bundleChanged(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();
}
}
}
/**
* Creates a new OsgiRegistry instance bound to a particular OSGi runtime.
* The only parameter must be an instance of a {@link BundleReference}.
* The parameter is typed as an Object to protect callers from having
* to load OSGi classes directly.
*
* @param bundleReference must be a non-null instance of a BundleReference
*/
OsgiRegistry(Object bundleReference) {
BundleReference br = (BundleReference)bundleReference;
bundleContext = br.getBundle().getBundleContext();
}
/**
* 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 (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();
}
Enumeration e = bundle.findEntries("META-INF/services/", "*", false);
if (e != null) {
while (e.hasMoreElements()) {
final URL u = (URL) 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(String serviceName) {
lock.readLock().lock();
try {
final List> result = new LinkedList>();
for (Map>>> value : factories.values()) {
if (value.containsKey(serviceName)) {
try {
result.addAll(value.get(serviceName).call());
} catch (Exception ex) {
// ignore
}
}
}
return result;
} finally {
lock.readLock().unlock();
}
}
}