org.glassfish.hk2.osgiresourcelocator.ServiceLoaderImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache Show documentation
Show all versions of ehcache Show documentation
Ehcache is an open source, standards-based cache used to boost performance,
offload the database and simplify scalability. Ehcache is robust, proven and full-featured and
this has made it the most widely-used Java-based cache.
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2009 Sun Microsystems, Inc. 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 https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/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 glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [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.hk2.osgiresourcelocator;
import org.osgi.framework.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author [email protected]
*/
public final class ServiceLoaderImpl extends org.glassfish.hk2.osgiresourcelocator.ServiceLoader {
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
private BundleListener bundleTracker;
private BundleContext bundleContext;
private ProvidersList providersList = new ProvidersList();
// /**
// * Map of service type to bundles providing the service
// */
// private Map> serviceToProvidersMap = new HashMap>();
public ServiceLoaderImpl() {
ClassLoader cl = getClass().getClassLoader();
if (cl instanceof BundleReference) {
bundleContext = getBundleContextSecured(BundleReference.class.cast(cl).getBundle());
}
if (bundleContext == null) {
throw new RuntimeException("There is no bundle context available yet. " +
"Instatiate this class in STARTING or ACTIVE state only");
}
}
private BundleContext getBundleContextSecured(final Bundle bundle) {
if (System.getSecurityManager() != null) {
return AccessController.doPrivileged(new PrivilegedAction() {
public BundleContext run() {
return bundle.getBundleContext();
}
});
} else {
return bundle.getBundleContext();
}
}
public void trackBundles() {
assert (bundleTracker == null);
/*
* The reason for separating this code from constructor is that we don't want to
* leak a partially constructed this reference via inner class called BundleTracker.
* That would be a problem since BundleTracker receives callbacks from other threads.
*/
// First register a listener and then iterate over existing bundles
bundleTracker = new BundleTracker();
bundleContext.addBundleListener(bundleTracker);
for (Bundle bundle : bundleContext.getBundles()) {
addProviders(bundle);
}
}
/*package*/ Iterable extends T> lookupProviderInstances1(Class serviceClass, ProviderFactory factory) {
if (factory == null) {
factory = new DefaultFactory();
}
List providers = new ArrayList();
for (Class c : lookupProviderClasses1(serviceClass)) {
try {
final T providerInstance = factory.make(c, serviceClass);
if (providerInstance != null) {
providers.add(providerInstance);
} else {
debug(factory + " returned null provider instance!!!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
return providers;
}
/*package*/ Iterable lookupProviderClasses1(Class serviceClass) {
List providerClasses = new ArrayList();
rwLock.readLock().lock();
try {
final String serviceName = serviceClass.getName();
for (ProvidersPerBundle providersPerBundle : providersList.getAllProviders()) {
final Bundle bundle = bundleContext.getBundle(providersPerBundle.getBundleId());
if (bundle == null) {
// bundle may have been uninstalled
continue;
}
final List providerNames = providersPerBundle.getServiceToProvidersMap().get(serviceName);
if (providerNames == null) {
continue;
}
for (String providerName : providerNames) {
try {
final Class providerClass = loadClassSecured(bundle, providerName);
if (isCompatible(providerClass, serviceClass)) {
providerClasses.add(providerClass);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
return providerClasses;
} finally {
rwLock.readLock().unlock();
}
}
private Class loadClassSecured(final Bundle bundle, final String name)
throws ClassNotFoundException {
if (System.getSecurityManager()!=null) {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction(){
public Class run() throws ClassNotFoundException {
return bundle.loadClass(name);
}
});
} catch (PrivilegedActionException e) {
throw ClassNotFoundException.class.cast(e.getException());
}
} else {
return bundle.loadClass(name);
}
}
private boolean isCompatible(Class providerClass, Class serviceClass) {
try {
// We can't do an isAssignable check, because the provider class may not be assignable to service class
// in cases like JAXBContextFactory. So, we try to see if provider sees the same service class or not.
// Apparently, they need not. e.g., consider the following situation:
// Bundle B contains interface p.I.class.
// Export-Package: p
//
// Bundle B1 contains class p1.C1.class, which implements p.I.class
// Export-Package: p1; uses p
// Import-Package: p
//
// Bundle B2 contains class p2.C2.class, which extends p1.C1.class
// Import-Package: p1 (Note, it does not import p)
//
// Now, p2.C2.class.getClassLoader().loadClass("p.I") will fail.
// In such a case, we shall return TRUE and catch any bad provider in lookupProviderInstances() method
// which will again do an isAssignable test. See DefaultFactory.make() for example.
final Class> serviceClassSeenByProviderClass = Class.forName(serviceClass.getName(), false, providerClass.getClassLoader());
final boolean isCompatible = serviceClassSeenByProviderClass == serviceClass;
if (!isCompatible) {
debug(providerClass + " loaded by " + providerClass.getClassLoader()
+ " sees " + serviceClass + " from " + serviceClassSeenByProviderClass.getClassLoader()
+ ", where as caller uses " + serviceClass + " loaded by " + serviceClass.getClassLoader());
}
return isCompatible;
} catch (ClassNotFoundException e) {
debug("Unable to reach " + serviceClass + " from " + providerClass + ", which is loaded by " + providerClass.getClassLoader(), e);
return true;
}
}
/**
* Loads a single service file and returns the names of the providers.
* If the same provider appears multiple times, the list contains only one entry for all of the duplicates.
*
* @return names of providers, empty list if none is found
*/
private List load(InputStream is) throws IOException {
List providerNames = new ArrayList();
try {
/*
* The format of service file is specified at
* http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#Service%20Provider
* According to the above spec,
* The file contains a list of fully-qualified binary names of
* concrete provider classes, one per line.
* Space and tab characters surrounding each name,
* as well as blank lines, are ignored.
* The comment character is '#' ('\u0023', NUMBER SIGN);
* on each line all characters following the first comment
* character are ignored. The file must be encoded in UTF-8.
*/
Scanner scanner = new Scanner(is);
final String commentPattern = "#"; // NOI18N
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if (!line.startsWith(commentPattern)) {
StringTokenizer st = new StringTokenizer(line);
while (st.hasMoreTokens()) {
providerNames.add(st.nextToken());
break; // Only one entry per line
}
}
}
} finally {
is.close();
}
return providerNames;
}
private class BundleTracker implements BundleListener {
public void bundleChanged(BundleEvent event) {
Bundle bundle = event.getBundle();
switch (event.getType()) {
case BundleEvent.INSTALLED:
addProviders(bundle);
break;
case BundleEvent.UNINSTALLED:
removeProviders(bundle);
break;
case BundleEvent.UPDATED:
removeProviders(bundle);
addProviders(bundle);
break;
}
}
}
private void addProviders(Bundle bundle) {
rwLock.writeLock().lock();
try {
final String SERVICE_LOCATION = "META-INF/services";
if (bundle.getEntry(SERVICE_LOCATION) == null) return;
Enumeration entries;
entries = bundle.getEntryPaths(SERVICE_LOCATION);
if (entries != null) {
ProvidersPerBundle providers = new ProvidersPerBundle(bundle.getBundleId());
while (entries.hasMoreElements()) {
String entry = entries.nextElement();
String serviceName = entry.substring(SERVICE_LOCATION.length() + 1);
InputStream is;
final URL url = bundle.getEntry(entry);
try {
is = url.openStream();
List providerNames = load(is);
debug("Bundle = " + bundle + ", serviceName = " + serviceName + ", providerNames = " + providerNames);
providers.put(serviceName, providerNames);
} catch (IOException e) {
}
}
providersList.addProviders(providers);
}
} finally {
rwLock.writeLock().unlock();
}
}
private synchronized void removeProviders(Bundle bundle) {
rwLock.writeLock().lock();
try {
providersList.removeProviders(bundle.getBundleId());
} finally {
rwLock.writeLock().unlock();
}
}
/**
* Map of service name to provider names for a particular bundle
*/
private static class ProvidersPerBundle {
private long bundleId;
Map> serviceToProvidersMap = new HashMap>();
private ProvidersPerBundle(long bundleId) {
this.bundleId = bundleId;
}
public long getBundleId() {
return bundleId;
}
public void put(String serviceName, List providerNames) {
serviceToProvidersMap.put(serviceName, providerNames);
}
public Map> getServiceToProvidersMap() {
return serviceToProvidersMap;
}
}
/**
* List of {@link ServiceLoaderImpl.ProvidersPerBundle}
* Synchronisation is handled by outer class.
*/
private static class ProvidersList {
// This list is sorted in ascending order of bundle id
private List allProviders = new LinkedList();
void addProviders(ProvidersPerBundle providers) {
long bundleId = providers.getBundleId();
int idx = 0;
Iterator iterator = getAllProviders().iterator();
while (iterator.hasNext()) {
ProvidersPerBundle providersPerBundle = iterator.next();
if (providersPerBundle.getBundleId() > bundleId) {
getAllProviders().add(idx, providers);
return;
}
}
getAllProviders().add(providers);
}
void removeProviders(long bundleId) {
Iterator iterator = getAllProviders().iterator();
while (iterator.hasNext()) {
ProvidersPerBundle providersPerBundle = iterator.next();
if (providersPerBundle.getBundleId() == bundleId) {
iterator.remove();
return;
}
}
}
/**
* @return a list of {@link ProvidersPerBundle} sorted in ascending order of bundle id.
*/
public List getAllProviders() {
return allProviders;
}
}
private static class DefaultFactory implements ProviderFactory {
public T make(Class providerClass, Class serviceClass) throws Exception {
if (serviceClass.isAssignableFrom(providerClass)) {
return (T) providerClass.newInstance();
}
return null;
}
}
private void debug(String s) {
if (Boolean.valueOf(bundleContext.getProperty("org.glassfish.hk2.osgiresourcelocator.debug"))) {
System.out.println("org.glassfish.hk2.osgiresourcelocator:DEBUG: " + s);
}
}
private void debug(String s, Throwable t) {
if (Boolean.valueOf(bundleContext.getProperty("org.glassfish.hk2.osgiresourcelocator.debug"))) {
System.out.println("org.glassfish.hk2.osgiresourcelocator:DEBUG: " + s);
t.printStackTrace(System.out);
}
}
}