| /******************************************************************************* |
| * Copyright (c) 2008, 2010 VMware Inc. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * VMware Inc. - initial contribution |
| *******************************************************************************/ |
| |
| package org.eclipse.virgo.kernel.userregion.internal.equinox; |
| |
| import java.io.IOException; |
| import java.lang.instrument.ClassFileTransformer; |
| import java.lang.instrument.IllegalClassFormatException; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.net.URL; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.security.ProtectionDomain; |
| import java.sql.Driver; |
| import java.sql.DriverManager; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.Vector; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| |
| import org.eclipse.osgi.baseadaptor.BaseData; |
| import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry; |
| import org.eclipse.osgi.baseadaptor.loader.ClasspathEntry; |
| import org.eclipse.osgi.baseadaptor.loader.ClasspathManager; |
| import org.eclipse.osgi.framework.adaptor.ClassLoaderDelegate; |
| import org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader; |
| import org.eclipse.osgi.service.resolver.PlatformAdmin; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleContext; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import org.eclipse.virgo.kernel.osgi.framework.ExtendedClassNotFoundException; |
| import org.eclipse.virgo.kernel.osgi.framework.ExtendedNoClassDefFoundError; |
| import org.eclipse.virgo.kernel.osgi.framework.InstrumentableClassLoader; |
| import org.eclipse.virgo.kernel.osgi.framework.OsgiFrameworkUtils; |
| |
| /** |
| * Extension to {@link DefaultClassLoader} that adds instrumentation support. |
| * <p/> |
| * |
| * <strong>Concurrent Semantics</strong><br /> |
| * |
| * As threadsafe as <code>DefaultClassLoader</code>. |
| * |
| */ |
| public final class KernelBundleClassLoader extends DefaultClassLoader implements InstrumentableClassLoader { |
| |
| static { |
| try { |
| Method parallelCapableMethod = ClassLoader.class.getDeclaredMethod("registerAsParallelCapable", (Class[]) null); |
| parallelCapableMethod.setAccessible(true); |
| parallelCapableMethod.invoke(null, new Object[0]); |
| } catch (Throwable e) { |
| // must avoid failing in clinit |
| } |
| } |
| |
| private static final String[] EXCLUDED_PACKAGES = new String[] { "java.", "javax.", "sun.", "oracle." }; |
| |
| private static final String HEADER_INSTRUMENT_PACKAGE = "Instrument-Package"; |
| |
| private static final Logger LOGGER = LoggerFactory.getLogger(KernelBundleClassLoader.class); |
| |
| private final List<ClassFileTransformer> classFileTransformers = new CopyOnWriteArrayList<ClassFileTransformer>(); |
| |
| private final String[] instrumentedPackages; |
| |
| private final String[] classpath; |
| |
| private final String bundleScope; |
| |
| private final Set<Class<Driver>> loadedDriverClasses = new HashSet<Class<Driver>>(); |
| |
| private final Object monitor = new Object(); |
| |
| private volatile boolean instrumented; |
| |
| /** |
| * Constructs a new <code>ServerBundleClassLoader</code>. |
| * |
| * @param parent the parent <code>ClassLoader</code>. |
| * @param delegate the delegate for this ClassLoader</code> |
| * @param domain the domain for this ClassLoader</code> |
| * @param bundledata the bundledata for this ClassLoader</code> |
| * @param classpath the classpath for this ClassLoader</code> |
| */ |
| KernelBundleClassLoader(ClassLoader parent, ClassLoaderDelegate delegate, ProtectionDomain domain, BaseData bundledata, String[] classpath) { |
| super(parent, delegate, domain, bundledata, classpath); |
| this.classpath = classpath; |
| this.bundleScope = OsgiFrameworkUtils.getScopeName(bundledata.getBundle()); |
| this.instrumentedPackages = findInstrumentedPackages(bundledata.getBundle()); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void addClassFileTransformer(ClassFileTransformer transformer) { |
| this.instrumented = true; |
| synchronized (this.classFileTransformers) { |
| if (this.classFileTransformers.contains(transformer)) { |
| return; |
| } |
| this.classFileTransformers.add(transformer); |
| } |
| Bundle[] bundles = getDependencyBundles(false); |
| for (Bundle bundle : bundles) { |
| if (propagateInstrumentationTo(bundle)) { |
| ClassLoader bundleClassLoader = getBundleClassLoader(bundle); |
| if (bundleClassLoader instanceof KernelBundleClassLoader) { |
| ((KernelBundleClassLoader) bundleClassLoader).addClassFileTransformer(transformer); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @param bundle |
| * @return |
| */ |
| private ClassLoader getBundleClassLoader(Bundle bundle) { |
| return EquinoxUtils.getBundleClassLoader(bundle); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { |
| try { |
| Class<?> loadedClass = super.loadClass(name, resolve); |
| storeClassIfDriver(loadedClass); |
| return loadedClass; |
| } catch (ClassNotFoundException e) { |
| throw new ExtendedClassNotFoundException(this, e); |
| } catch (NoClassDefFoundError e) { |
| throw new ExtendedNoClassDefFoundError(this, e); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public boolean isInstrumented() { |
| return this.instrumented; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public int getClassFileTransformerCount() { |
| return this.classFileTransformers.size(); |
| } |
| |
| /** |
| * Finds the explicit list of packages to include in instrumentation (if specified). |
| */ |
| private String[] findInstrumentedPackages(Bundle bundle) { |
| String headerValue = (String) bundle.getHeaders().get(HEADER_INSTRUMENT_PACKAGE); |
| if (headerValue == null || headerValue.length() == 0) { |
| return new String[0]; |
| } else { |
| String[] vals = headerValue.split(","); |
| String[] packageNames = new String[vals.length]; |
| for (int x = 0; x < packageNames.length; x++) { |
| packageNames[x] = vals[x].trim(); |
| } |
| return packageNames; |
| } |
| } |
| |
| private boolean propagateInstrumentationTo(Bundle bundle) { |
| return !EquinoxUtils.isSystemBundle(bundle) && this.bundleScope != null && this.bundleScope.equals(OsgiFrameworkUtils.getScopeName(bundle)); |
| } |
| |
| private boolean shouldInstrument(String className) { |
| return includedForInstrumentation(className) && !excludedFromInstrumentation(className); |
| } |
| |
| private boolean includedForInstrumentation(String className) { |
| if (this.instrumentedPackages.length == 0) { |
| return true; |
| } |
| for (String instrumentedPackage : this.instrumentedPackages) { |
| if (className.startsWith(instrumentedPackage)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean excludedFromInstrumentation(String className) { |
| for (String excludedPackage : EXCLUDED_PACKAGES) { |
| if (className.startsWith(excludedPackage)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public ThrowAwayClassLoader createThrowAway() { |
| final ClasspathManager manager = new ClasspathManager(this.manager.getBaseData(), this.classpath, this); |
| manager.initialize(); |
| return AccessController.doPrivileged(new PrivilegedAction<ThrowAwayClassLoader>() { |
| |
| public ThrowAwayClassLoader run() { |
| return new ThrowAwayClassLoader(manager); |
| } |
| }); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Class<?> defineClass(String name, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry) { |
| |
| byte[] transformedBytes = classbytes; |
| if (shouldInstrument(name)) { |
| for (ClassFileTransformer transformer : this.classFileTransformers) { |
| try { |
| String transformName = name.replaceAll("\\.", "/"); |
| byte[] transform = transformer.transform(this, transformName, null, this.domain, transformedBytes); |
| if (transform != null) { |
| transformedBytes = transform; |
| } |
| } catch (IllegalClassFormatException e) { |
| throw new ClassFormatError("Error reading class from bundle entry '" + entry.getName() + "'. " + e.getMessage()); |
| } |
| } |
| } |
| try { |
| Class<?> definedClass = super.defineClass(name, transformedBytes, classpathEntry, entry); |
| storeClassIfDriver(definedClass); |
| return definedClass; |
| } catch (NoClassDefFoundError e) { |
| throw new ExtendedNoClassDefFoundError(this, e); |
| } |
| } |
| |
| /** |
| * @param definedClass |
| */ |
| @SuppressWarnings("unchecked") |
| private void storeClassIfDriver(Class<?> candidateClass) { |
| if (Driver.class.isAssignableFrom(candidateClass)) { |
| synchronized (this.monitor) { |
| this.loadedDriverClasses.add((Class<Driver>) candidateClass); |
| } |
| } |
| } |
| |
| @Override |
| public void close() { |
| clearJdbcDrivers(); |
| } |
| |
| private void clearJdbcDrivers() { |
| Set<Class<Driver>> localLoadedDriverClasses; |
| synchronized (this.monitor) { |
| localLoadedDriverClasses = new HashSet<Class<Driver>>(this.loadedDriverClasses); |
| } |
| |
| synchronized (DriverManager.class) { |
| try { // Java 6 |
| Field writeDriversField = DriverManager.class.getDeclaredField("writeDrivers"); |
| writeDriversField.setAccessible(true); |
| Vector<?> writeDrivers = (Vector<?>) writeDriversField.get(null); |
| |
| Iterator<?> driverElements = writeDrivers.iterator(); |
| |
| while (driverElements.hasNext()) { |
| Object driverObj = driverElements.next(); |
| Field driverField = driverObj.getClass().getDeclaredField("driver"); |
| driverField.setAccessible(true); |
| if (localLoadedDriverClasses.contains(driverField.get(driverObj).getClass())) { |
| driverElements.remove(); |
| } |
| } |
| |
| Vector<?> readDrivers = (Vector<?>) writeDrivers.clone(); |
| Field readDriversField = DriverManager.class.getDeclaredField("readDrivers"); |
| readDriversField.setAccessible(true); |
| readDriversField.set(null, readDrivers); |
| LOGGER.debug("Cleared JDBC drivers for " + this + " using Java 6 strategy"); |
| } catch (Exception javaSixWayFailed) { |
| try { // Java 7 |
| Field registeredDriversField = DriverManager.class.getDeclaredField("registeredDrivers"); |
| registeredDriversField.setAccessible(true); |
| CopyOnWriteArrayList<?> registeredDrivers = (CopyOnWriteArrayList<?>) registeredDriversField.get(null); |
| |
| Iterator<?> driverElements = registeredDrivers.iterator(); |
| List<Object> driverElementsToRemove = new ArrayList<Object>(); |
| |
| while (driverElements.hasNext()) { |
| Object driverObj = driverElements.next(); |
| Field driverField = driverObj.getClass().getDeclaredField("driver"); |
| driverField.setAccessible(true); |
| if (localLoadedDriverClasses.contains(driverField.get(driverObj).getClass())) { |
| driverElementsToRemove.add(driverObj); |
| } |
| } |
| |
| for (Object driverObj : driverElementsToRemove) { |
| registeredDrivers.remove(driverObj); |
| } |
| LOGGER.debug("Cleared JDBC drivers for " + this + " using Java 7 strategy"); |
| } catch (Exception e) { |
| LOGGER.warn("Failure when clearing JDBC drivers for " + this, e); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("%s: [bundle=%s]", getClass().getSimpleName(), this.delegate); |
| } |
| |
| private Bundle[] getDependencyBundles(boolean includeDependenciesFragments) { |
| Bundle bundle = this.manager.getBaseData().getBundle(); |
| BundleContext systemBundleContext = getBundleContext(); |
| PlatformAdmin serverAdmin = getPlatformAdmin(); |
| Bundle[] deps = EquinoxUtils.getDirectDependencies(bundle, systemBundleContext, serverAdmin, includeDependenciesFragments); |
| return deps; |
| } |
| |
| /** |
| * Gets the {@link BundleContext} for this ClassLoader's {@link Bundle}. |
| * |
| * @return the <code>BundleContext</code>. |
| */ |
| private BundleContext getBundleContext() { |
| return this.manager.getBaseData().getAdaptor().getContext(); |
| } |
| |
| /** |
| * Gets the {@link PlatformAdmin} service. |
| * |
| * @return the <code>PlatformAdmin</code> service. |
| */ |
| private PlatformAdmin getPlatformAdmin() { |
| return this.manager.getBaseData().getAdaptor().getPlatformAdmin(); |
| } |
| |
| /** |
| * Throwaway classloader for OSGi bundles. |
| * <p/> |
| * |
| * <strong>Concurrent Semantics</strong><br /> |
| * |
| * As threadsafe as {@link ClassLoader}. |
| * |
| */ |
| final class ThrowAwayClassLoader extends ClassLoader { |
| |
| private final ConcurrentMap<String, Class<?>> loadedClasses = new ConcurrentHashMap<String, Class<?>>(); |
| |
| private final ClasspathManager manager; |
| |
| /** |
| * @param manager |
| */ |
| private ThrowAwayClassLoader(ClasspathManager manager) { |
| this.manager = manager; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { |
| if (!shouldInstrument(name)) { |
| return KernelBundleClassLoader.this.loadClass(name, resolve); |
| } |
| Class<?> cls = KernelBundleClassLoader.this.findLoadedClass(name); |
| if (cls == null) { |
| cls = this.loadedClasses.get(name); |
| if (cls == null) { |
| cls = findClassInternal(name, true); |
| if (cls == null) { |
| cls = KernelBundleClassLoader.this.loadClass(name, resolve); |
| } |
| } |
| } |
| if (cls == null) { |
| throw new ClassNotFoundException(name); |
| } |
| if (resolve) { |
| resolveClass(cls); |
| } |
| // TODO review findbugs warning vs. failing tests in LoadTimeWeavingTests |
| this.loadedClasses.putIfAbsent(name, cls); |
| return cls; |
| } |
| |
| /** |
| * Attempts to find a <code>Class</code> from the enclosing bundle. |
| * |
| * @param name the name of the <code>Class</code> |
| * @param traverseDependencies should dependency bundles be checked for the class. |
| */ |
| Class<?> findClassInternal(String name, boolean traverseDependencies) { |
| String path = name.replaceAll("\\.", "/").concat(".class"); |
| |
| BundleEntry entry = this.manager.findLocalEntry(path); |
| if (entry == null) { |
| if (traverseDependencies) { |
| return findClassFromImport(name); |
| } else { |
| return null; |
| } |
| } |
| byte[] bytes; |
| try { |
| bytes = entry.getBytes(); |
| } catch (IOException e) { |
| bytes = null; |
| } |
| return bytes == null ? null : defineClass(name, bytes, 0, bytes.length); |
| } |
| |
| /** |
| * Attempts to locate a <code>Class</code> from one of the imported bundles. |
| * |
| * @param name the <code>Class</code> name. |
| * @return the located <code>Class</code>, or <code>null</code> if no <code>Class</code> can be found. |
| */ |
| private Class<?> findClassFromImport(String name) { |
| Bundle[] deps = getDependencyBundles(false); |
| for (Bundle dep : deps) { |
| ClassLoader depClassLoader = getBundleClassLoader(dep); |
| if (depClassLoader instanceof KernelBundleClassLoader) { |
| KernelBundleClassLoader pbcl = (KernelBundleClassLoader) depClassLoader; |
| Class<?> loadedClass = pbcl.publicFindLoaded(name); |
| if (loadedClass != null) { |
| return loadedClass; |
| } |
| ThrowAwayClassLoader throwAway = pbcl.createThrowAway(); |
| Class<?> cls = throwAway.findClassInternal(name, false); |
| if (cls != null) { |
| return cls; |
| } |
| } |
| |
| } |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public URL getResource(String name) { |
| return KernelBundleClassLoader.this.getResource(name); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Enumeration<URL> getResources(String name) throws IOException { |
| return KernelBundleClassLoader.this.getResources(name); |
| } |
| } |
| } |