blob: cee0d2135f0b794b9be4c630d458c72321eba881 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.felix.framework;
import org.apache.felix.framework.BundleWiringImpl.BundleClassLoader;
import org.apache.felix.framework.cache.Content;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.hooks.weaving.WeavingException;
import org.osgi.framework.hooks.weaving.WeavingHook;
import org.osgi.framework.hooks.weaving.WovenClass;
import org.osgi.framework.hooks.weaving.WovenClassListener;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleWire;
import org.osgi.framework.wiring.BundleWiring;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class BundleWiringImplTest
{
private BundleWiringImpl bundleWiring;
private StatefulResolver mockResolver;
private BundleRevisionImpl mockRevisionImpl;
private BundleImpl mockBundle;
@SuppressWarnings("rawtypes")
public void initializeSimpleBundleWiring() throws Exception
{
mockResolver = mock(StatefulResolver.class);
mockRevisionImpl = mock(BundleRevisionImpl.class);
mockBundle = mock(BundleImpl.class);
Logger logger = new Logger();
Map configMap = new HashMap();
List<BundleRevision> fragments = new ArrayList<BundleRevision>();
List<BundleWire> wires = new ArrayList<BundleWire>();
Map<String, BundleRevision> importedPkgs = new HashMap<String, BundleRevision>();
Map<String, List<BundleRevision>> requiredPkgs = new HashMap<String, List<BundleRevision>>();
when(mockRevisionImpl.getBundle()).thenReturn(mockBundle);
when(mockBundle.getBundleId()).thenReturn(Long.valueOf(1));
bundleWiring = new BundleWiringImpl(logger, configMap, mockResolver,
mockRevisionImpl, fragments, wires, importedPkgs, requiredPkgs);
}
@Test
public void testBundleClassLoader() throws Exception
{
bundleWiring = mock(BundleWiringImpl.class);
BundleClassLoader bundleClassLoader = createBundleClassLoader(
BundleClassLoader.class, bundleWiring);
assertNotNull(bundleClassLoader);
}
@SuppressWarnings("rawtypes")
@Test
public void testFindClassNonExistant() throws Exception
{
initializeSimpleBundleWiring();
BundleClassLoader bundleClassLoader = createBundleClassLoader(
BundleClassLoader.class, bundleWiring);
assertNotNull(bundleClassLoader);
Class foundClass = null;
try
{
foundClass = bundleClassLoader
.findClass("org.apache.felix.test.NonExistant");
} catch (ClassNotFoundException e)
{
fail("Class should not throw exception");
}
assertNull("Nonexistant Class Should be null", foundClass);
}
@SuppressWarnings("rawtypes")
@Test
public void testFindClassExistant() throws Exception
{
Felix mockFramework = mock(Felix.class);
HookRegistry hReg = mock(HookRegistry.class);
Mockito.when(mockFramework.getHookRegistry()).thenReturn(hReg);
Content mockContent = mock(Content.class);
Class testClass = TestClass.class;
String testClassName = testClass.getName();
String testClassAsPath = testClassName.replace('.', '/') + ".class";
byte[] testClassBytes = createTestClassBytes(testClass, testClassAsPath);
List<Content> contentPath = new ArrayList<Content>();
contentPath.add(mockContent);
initializeSimpleBundleWiring();
when(mockBundle.getFramework()).thenReturn(mockFramework);
when(mockFramework.getBootPackages()).thenReturn(new String[0]);
when(mockRevisionImpl.getContentPath()).thenReturn(contentPath);
when(mockContent.getEntryAsBytes(testClassAsPath)).thenReturn(
testClassBytes);
BundleClassLoader bundleClassLoader = createBundleClassLoader(
BundleClassLoader.class, bundleWiring);
assertNotNull(bundleClassLoader);
Class foundClass = null;
try
{
foundClass = bundleClassLoader.findClass(TestClass.class.getName());
} catch (ClassNotFoundException e)
{
fail("Class should not throw exception");
}
assertNotNull("Class Should be found in this classloader", foundClass);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
public void testFindClassWeave() throws Exception
{
Felix mockFramework = mock(Felix.class);
Content mockContent = mock(Content.class);
ServiceReference<WeavingHook> mockServiceReferenceWeavingHook = mock(ServiceReference.class);
ServiceReference<WovenClassListener> mockServiceReferenceWovenClassListener = mock(ServiceReference.class);
Set<ServiceReference<WeavingHook>> hooks = new HashSet<ServiceReference<WeavingHook>>();
hooks.add(mockServiceReferenceWeavingHook);
DummyWovenClassListener dummyWovenClassListener = new DummyWovenClassListener();
Set<ServiceReference<WovenClassListener>> listeners = new HashSet<ServiceReference<WovenClassListener>>();
listeners.add(mockServiceReferenceWovenClassListener);
Class testClass = TestClass.class;
String testClassName = testClass.getName();
String testClassAsPath = testClassName.replace('.', '/') + ".class";
byte[] testClassBytes = createTestClassBytes(testClass, testClassAsPath);
List<Content> contentPath = new ArrayList<Content>();
contentPath.add(mockContent);
initializeSimpleBundleWiring();
when(mockBundle.getFramework()).thenReturn(mockFramework);
when(mockFramework.getBootPackages()).thenReturn(new String[0]);
when(mockRevisionImpl.getContentPath()).thenReturn(contentPath);
when(mockContent.getEntryAsBytes(testClassAsPath)).thenReturn(
testClassBytes);
HookRegistry hReg = mock(HookRegistry.class);
when(hReg.getHooks(WeavingHook.class)).thenReturn(hooks);
when(mockFramework.getHookRegistry()).thenReturn(hReg);
when(
mockFramework.getService(mockFramework,
mockServiceReferenceWeavingHook, false)).thenReturn(
new GoodDummyWovenHook());
when(hReg.getHooks(WovenClassListener.class)).thenReturn(
listeners);
when(
mockFramework.getService(mockFramework,
mockServiceReferenceWovenClassListener, false))
.thenReturn(dummyWovenClassListener);
BundleClassLoader bundleClassLoader = createBundleClassLoader(
BundleClassLoader.class, bundleWiring);
assertNotNull(bundleClassLoader);
Class foundClass = null;
try
{
foundClass = bundleClassLoader.findClass(TestClass.class.getName());
} catch (ClassNotFoundException e)
{
fail("Class should not throw exception");
}
assertNotNull("Class Should be found in this classloader", foundClass);
assertEquals("Weaving should have added a field", 1,
foundClass.getFields().length);
assertEquals("There should be 2 state changes fired by the weaving", 2,
dummyWovenClassListener.stateList.size());
assertEquals("The first state change should transform the class",
(Object)WovenClass.TRANSFORMED,
dummyWovenClassListener.stateList.get(0));
assertEquals("The second state change should define the class",
(Object)WovenClass.DEFINED, dummyWovenClassListener.stateList.get(1));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
public void testFindClassBadWeave() throws Exception
{
Felix mockFramework = mock(Felix.class);
Content mockContent = mock(Content.class);
ServiceReference<WeavingHook> mockServiceReferenceWeavingHook = mock(ServiceReference.class);
ServiceReference<WovenClassListener> mockServiceReferenceWovenClassListener = mock(ServiceReference.class);
Set<ServiceReference<WeavingHook>> hooks = new HashSet<ServiceReference<WeavingHook>>();
hooks.add(mockServiceReferenceWeavingHook);
DummyWovenClassListener dummyWovenClassListener = new DummyWovenClassListener();
Set<ServiceReference<WovenClassListener>> listeners = new HashSet<ServiceReference<WovenClassListener>>();
listeners.add(mockServiceReferenceWovenClassListener);
Class testClass = TestClass.class;
String testClassName = testClass.getName();
String testClassAsPath = testClassName.replace('.', '/') + ".class";
byte[] testClassBytes = createTestClassBytes(testClass, testClassAsPath);
List<Content> contentPath = new ArrayList<Content>();
contentPath.add(mockContent);
initializeSimpleBundleWiring();
when(mockBundle.getFramework()).thenReturn(mockFramework);
when(mockFramework.getBootPackages()).thenReturn(new String[0]);
when(mockRevisionImpl.getContentPath()).thenReturn(contentPath);
when(mockContent.getEntryAsBytes(testClassAsPath)).thenReturn(
testClassBytes);
HookRegistry hReg = mock(HookRegistry.class);
when(hReg.getHooks(WeavingHook.class)).thenReturn(hooks);
when(mockFramework.getHookRegistry()).thenReturn(hReg);
when(
mockFramework.getService(mockFramework,
mockServiceReferenceWeavingHook, false)).thenReturn(
new BadDummyWovenHook());
when(hReg.getHooks(WovenClassListener.class)).thenReturn(
listeners);
when(
mockFramework.getService(mockFramework,
mockServiceReferenceWovenClassListener, false))
.thenReturn(dummyWovenClassListener);
BundleClassLoader bundleClassLoader = createBundleClassLoader(
BundleClassLoader.class, bundleWiring);
assertNotNull(bundleClassLoader);
try
{
bundleClassLoader.findClass(TestClass.class.getName());
fail("Class should throw exception");
} catch (Error e)
{
// This is expected
}
assertEquals("There should be 1 state changes fired by the weaving", 1,
dummyWovenClassListener.stateList.size());
assertEquals(
"The only state change should be a failed transform on the class",
(Object)WovenClass.TRANSFORMING_FAILED,
dummyWovenClassListener.stateList.get(0));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
public void testFindClassWeaveDefineError() throws Exception
{
Felix mockFramework = mock(Felix.class);
Content mockContent = mock(Content.class);
ServiceReference<WeavingHook> mockServiceReferenceWeavingHook = mock(ServiceReference.class);
ServiceReference<WovenClassListener> mockServiceReferenceWovenClassListener = mock(ServiceReference.class);
Set<ServiceReference<WeavingHook>> hooks = new HashSet<ServiceReference<WeavingHook>>();
hooks.add(mockServiceReferenceWeavingHook);
DummyWovenClassListener dummyWovenClassListener = new DummyWovenClassListener();
Set<ServiceReference<WovenClassListener>> listeners = new HashSet<ServiceReference<WovenClassListener>>();
listeners.add(mockServiceReferenceWovenClassListener);
Class testClass = TestClass.class;
String testClassName = testClass.getName();
String testClassAsPath = testClassName.replace('.', '/') + ".class";
byte[] testClassBytes = createTestClassBytes(testClass, testClassAsPath);
List<Content> contentPath = new ArrayList<Content>();
contentPath.add(mockContent);
initializeSimpleBundleWiring();
when(mockBundle.getFramework()).thenReturn(mockFramework);
when(mockFramework.getBootPackages()).thenReturn(new String[0]);
when(mockRevisionImpl.getContentPath()).thenReturn(contentPath);
when(mockContent.getEntryAsBytes(testClassAsPath)).thenReturn(
testClassBytes);
HookRegistry hReg = mock(HookRegistry.class);
when(hReg.getHooks(WeavingHook.class)).thenReturn(hooks);
when(mockFramework.getHookRegistry()).thenReturn(hReg);
when(
mockFramework.getService(mockFramework,
mockServiceReferenceWeavingHook, false)).thenReturn(
new BadDefineWovenHook());
when(hReg.getHooks(WovenClassListener.class)).thenReturn(
listeners);
when(
mockFramework.getService(mockFramework,
mockServiceReferenceWovenClassListener, false))
.thenReturn(dummyWovenClassListener);
BundleClassLoader bundleClassLoader = createBundleClassLoader(
BundleClassLoader.class, bundleWiring);
assertNotNull(bundleClassLoader);
try
{
bundleClassLoader.findClass(TestClass.class.getName());
fail("Class should throw exception");
} catch (Throwable e)
{
}
assertEquals("There should be 2 state changes fired by the weaving", 2,
dummyWovenClassListener.stateList.size());
assertEquals("The first state change should transform the class",
(Object)WovenClass.TRANSFORMED,
dummyWovenClassListener.stateList.get(0));
assertEquals("The second state change failed the define on the class",
(Object)WovenClass.DEFINE_FAILED,
dummyWovenClassListener.stateList.get(1));
}
private ConcurrentHashMap<String, ClassLoader> getAccessorCache(BundleWiringImpl wiring) throws NoSuchFieldException, IllegalAccessException {
Field m_accessorLookupCache = BundleWiringImpl.class.getDeclaredField("m_accessorLookupCache");
m_accessorLookupCache.setAccessible(true);
return (ConcurrentHashMap<String, ClassLoader>) m_accessorLookupCache.get(wiring);
}
@Test
public void testFirstGeneratedAccessorSkipClassloading() throws Exception
{
String classToBeLoaded = "sun.reflect.GeneratedMethodAccessor21";
Felix mockFramework = mock(Felix.class);
when(mockFramework.getBootPackages()).thenReturn(new String[0]);
initializeSimpleBundleWiring();
when(bundleWiring.getBundle().getFramework()).thenReturn(mockFramework);
BundleClassLoader bundleClassLoader = createBundleClassLoader(
BundleClassLoader.class, bundleWiring);
assertNotNull(bundleClassLoader);
try {
bundleClassLoader.loadClass(classToBeLoaded, true);
fail();
} catch (ClassNotFoundException cnf) {
//this is expected
//make sure boot delegation was done before CNF was thrown
verify(mockFramework).getBootPackages();
//make sure the class is added to the skip class cache
assertEquals(getAccessorCache(bundleWiring).get(classToBeLoaded), BundleWiringImpl.CNFE_CLASS_LOADER);
} catch (Exception e) {
e.printStackTrace();
fail();
}
}
@SuppressWarnings("rawtypes")
public void initializeBundleWiringWithImportsAndRequired(Map<String, BundleRevision> importedPkgs, Map<String, List<BundleRevision>> requiredPkgs) throws Exception
{
mockResolver = mock(StatefulResolver.class);
mockRevisionImpl = mock(BundleRevisionImpl.class);
mockBundle = mock(BundleImpl.class);
Logger logger = new Logger();
Map configMap = new HashMap();
List<BundleRevision> fragments = new ArrayList<BundleRevision>();
List<BundleWire> wires = new ArrayList<BundleWire>();
when(mockRevisionImpl.getBundle()).thenReturn(mockBundle);
when(mockBundle.getBundleId()).thenReturn(Long.valueOf(1));
bundleWiring = new BundleWiringImpl(logger, configMap, mockResolver,
mockRevisionImpl, fragments, wires, importedPkgs, requiredPkgs);
}
@Test
public void testAccessorFirstLoadFailed() throws Exception
{
String classToBeLoaded = "sun.reflect.GeneratedMethodAccessor21";
Felix mockFramework = mock(Felix.class);
when(mockFramework.getBootPackages()).thenReturn(new String[0]);
Map<String, BundleRevision> importedPkgs = mock(Map.class);
Map<String, List<BundleRevision>> requiredPkgs = mock(Map.class);
initializeBundleWiringWithImportsAndRequired(importedPkgs, requiredPkgs);
when(bundleWiring.getBundle().getFramework()).thenReturn(mockFramework);
BundleClassLoader bundleClassLoader = createBundleClassLoader(
BundleClassLoader.class, bundleWiring);
assertNotNull(bundleClassLoader);
try {
bundleClassLoader.loadClass(classToBeLoaded, true);
fail();
} catch (ClassNotFoundException cnf) {
//this is expected
//make sure boot delegation was done before CNF was thrown
verify(mockFramework).getBootPackages();
//make sure imported and required pkgs are searched
verify(importedPkgs).values();
verify(requiredPkgs).values();
//make sure the class is added to the skip class cache
assertEquals(getAccessorCache(bundleWiring).get(classToBeLoaded), BundleWiringImpl.CNFE_CLASS_LOADER);
} catch (Exception e) {
e.printStackTrace();
fail();
}
}
@Test
public void testAccessorSubsequentLoadFailed() throws Exception
{
String classToBeLoaded = "sun.reflect.GeneratedMethodAccessor21";
Felix mockFramework = mock(Felix.class);
when(mockFramework.getBootPackages()).thenReturn(new String[0]);
Map<String, BundleRevision> importedPkgs = mock(Map.class);
Map<String, List<BundleRevision>> requiredPkgs = mock(Map.class);
initializeBundleWiringWithImportsAndRequired(importedPkgs, requiredPkgs);
when(bundleWiring.getBundle().getFramework()).thenReturn(mockFramework);
BundleClassLoader bundleClassLoader = createBundleClassLoader(
BundleClassLoader.class, bundleWiring);
assertNotNull(bundleClassLoader);
//first attempt to populate the cache
try {
bundleClassLoader.loadClass(classToBeLoaded, true);
fail();
} catch (ClassNotFoundException cnf) {
//this is expected
}
//now test that the subsequent class load throws CNF with out boot delegation and import/required packages
try {
importedPkgs = mock(Map.class);
requiredPkgs = mock(Map.class);
initializeBundleWiringWithImportsAndRequired(importedPkgs, requiredPkgs);
mockFramework = mock(Felix.class);
when(mockFramework.getBootPackages()).thenReturn(new String[0]);
when(bundleWiring.getBundle().getFramework()).thenReturn(mockFramework);
bundleClassLoader.loadClass(classToBeLoaded, true);
fail();
} catch (ClassNotFoundException cnf) {
//this is expected
//make sure boot delegation was not used
verify(mockFramework, never()).getBootPackages();
//make sure boot import and required packages were not searched
verify(importedPkgs, never()).values();
verify(requiredPkgs, never()).values();
} catch (Exception e) {
e.printStackTrace();
fail();
}
}
private BundleRevision getBundleRevision(String classToBeLoaded, BundleClassLoader pkgBundleClassLoader, Object value) throws ClassNotFoundException {
BundleRevision bundleRevision = mock(BundleRevision.class);
BundleWiring pkgBundleWiring = mock(BundleWiring.class);
when(pkgBundleClassLoader.findLoadedClassInternal(classToBeLoaded)).thenAnswer(createAnswer(value));
when(pkgBundleClassLoader.loadClass(classToBeLoaded)).thenAnswer(createAnswer(value));
when(pkgBundleWiring.getClassLoader()).thenReturn(pkgBundleClassLoader);
when(bundleRevision.getWiring()).thenReturn(pkgBundleWiring);
return bundleRevision;
}
@Test
public void testAccessorLoadImportPackage() throws Exception
{
String classToBeLoaded = "sun.reflect.GeneratedMethodAccessor21";
Felix mockFramework = mock(Felix.class);
when(mockFramework.getBootPackages()).thenReturn(new String[0]);
Map<String, BundleRevision> importedPkgs = mock(Map.class);
BundleClassLoader foundClassLoader = mock(BundleClassLoader.class);
BundleClassLoader notFoundClassLoader = mock(BundleClassLoader.class);
BundleRevision bundleRevision1 = getBundleRevision(classToBeLoaded, foundClassLoader, String.class);
BundleRevision bundleRevision2 = getBundleRevision(classToBeLoaded, notFoundClassLoader, null);
Map<String, BundleRevision> importedPkgsActual = new LinkedHashMap<String, BundleRevision>();
importedPkgsActual.put("sun.reflect1", bundleRevision1);
importedPkgsActual.put("sun.reflect2", bundleRevision2);
when(importedPkgs.values()).thenReturn(importedPkgsActual.values());
Map<String, List<BundleRevision>> requiredPkgs = mock(Map.class);
initializeBundleWiringWithImportsAndRequired(importedPkgs, requiredPkgs);
when(bundleWiring.getBundle().getFramework()).thenReturn(mockFramework);
BundleClassLoader bundleClassLoader = createBundleClassLoader(
BundleClassLoader.class, bundleWiring);
assertNotNull(bundleClassLoader);
//call class load to populate the cache
try {
Object result = bundleClassLoader.loadClass(classToBeLoaded, true);
assertNotNull(result);
assertTrue(getAccessorCache(bundleWiring).containsKey(classToBeLoaded));
assertEquals(getAccessorCache(bundleWiring).get(classToBeLoaded), foundClassLoader);
verify(foundClassLoader, times(1)).findLoadedClassInternal(classToBeLoaded);
verify(notFoundClassLoader, never()).findLoadedClassInternal(classToBeLoaded);
} catch (Exception e) {
fail();
}
//now make sure subsequent class load happens from cached revision
Object result = bundleClassLoader.loadClass(classToBeLoaded, true);
assertNotNull(result);
//makes sure the look up cache is accessed and the class is loaded from cached revision
verify(foundClassLoader, times(1)).findLoadedClassInternal(classToBeLoaded);
verify(foundClassLoader, times(1)).loadClass(classToBeLoaded);
verify(notFoundClassLoader, never()).findLoadedClassInternal(classToBeLoaded);
}
private static <T> Answer<T> createAnswer(final T value) {
Answer<T> dummy = new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
return value;
}
};
return dummy;
}
@Test
public void testAccessorBootDelegate() throws Exception
{
String classToBeLoaded = "sun.reflect.GeneratedMethodAccessor21";
Felix mockFramework = mock(Felix.class);
when(mockFramework.getBootPackages()).thenReturn(new String[0]);
Map<String, BundleRevision> importedPkgs = mock(Map.class);
BundleRevision bundleRevision1 = mock(BundleRevision.class);
Map<String, BundleRevision> importedPkgsActual = new HashMap<String, BundleRevision>();
importedPkgsActual.put("sun.reflect1", bundleRevision1);
when(importedPkgs.values()).thenReturn(importedPkgsActual.values());
Map<String, List<BundleRevision>> requiredPkgs = mock(Map.class);
ClassLoader bootDelegateClassLoader = mock(ClassLoader.class);
when(bootDelegateClassLoader.loadClass(classToBeLoaded)).thenAnswer(createAnswer(String.class));
initializeBundleWiringWithImportsAndRequired(importedPkgs, requiredPkgs);
when(bundleWiring.getBundle().getFramework()).thenReturn(mockFramework);
Field field = bundleWiring.getClass().getDeclaredField("m_bootClassLoader");
field.setAccessible(true);
field.set(bundleWiring, bootDelegateClassLoader);
BundleClassLoader bundleClassLoader = createBundleClassLoader(
BundleClassLoader.class, bundleWiring);
assertNotNull(bundleClassLoader);
try {
Object result = bundleClassLoader.loadClass(classToBeLoaded, true);
assertNotNull(result);
verify(importedPkgs, never()).values();
verify(requiredPkgs, never()).values();
assertTrue(getAccessorCache(bundleWiring).containsKey(classToBeLoaded));
assertTrue(getAccessorCache(bundleWiring).get(classToBeLoaded) == bootDelegateClassLoader);
} catch (Exception e) {
fail();
}
//now make sure subsequent class loading happens from boot delegation
Object result = bundleClassLoader.loadClass(classToBeLoaded, true);
assertNotNull(result);
//makes sure the look up cache is accessed and the class is loaded via boot delegation
verify(importedPkgs, never()).values();
verify(requiredPkgs, never()).values();
}
@Test
public void testParallelClassload() throws Exception
{
Felix mockFramework = mock(Felix.class);
HookRegistry hReg = mock(HookRegistry.class);
Mockito.when(mockFramework.getHookRegistry()).thenReturn(hReg);
Content mockContent = mock(Content.class);
final Class testClass = TestClassSuper.class;
final String testClassName = testClass.getName();
final String testClassAsPath = testClassName.replace('.', '/') + ".class";
byte[] testClassBytes = createTestClassBytes(testClass, testClassAsPath);
final Class testClass2 = TestClassChild.class;
final String testClassName2 = testClass2.getName();
final String testClassAsPath2 = testClassName2.replace('.', '/') + ".class";
byte[] testClassBytes2 = createTestClassBytes(testClass2, testClassAsPath2);
final Class testClass3 = TestClass.class;
final String testClassName3 = testClass3.getName();
final String testClassAsPath3 = testClassName3.replace('.', '/') + ".class";
byte[] testClassBytes3 = createTestClassBytes(testClass3, testClassAsPath3);
List<Content> contentPath = new ArrayList<Content>();
contentPath.add(mockContent);
BundleWiringImpl bundleWiring;
StatefulResolver mockResolver;
BundleRevisionImpl mockRevisionImpl;
BundleImpl mockBundle;
mockResolver = mock(StatefulResolver.class);
mockRevisionImpl = mock(BundleRevisionImpl.class);
mockBundle = mock(BundleImpl.class);
Logger logger = new Logger();
Map configMap = new HashMap();
List<BundleRevision> fragments = new ArrayList<BundleRevision>();
List<BundleWire> wires = new ArrayList<BundleWire>();
Map<String, BundleRevision> importedPkgs = new HashMap<String, BundleRevision>();
Map<String, List<BundleRevision>> requiredPkgs = new HashMap<String, List<BundleRevision>>();
when(mockRevisionImpl.getBundle()).thenReturn(mockBundle);
when(mockBundle.getBundleId()).thenReturn(Long.valueOf(1));
bundleWiring = new BundleWiringImpl(logger, configMap, mockResolver,
mockRevisionImpl, fragments, wires, importedPkgs, requiredPkgs);
when(mockBundle.getFramework()).thenReturn(mockFramework);
when(mockFramework.getBootPackages()).thenReturn(new String[0]);
when(mockRevisionImpl.getContentPath()).thenReturn(contentPath);
when(mockContent.getEntryAsBytes(testClassAsPath)).thenReturn(
testClassBytes);
when(mockContent.getEntryAsBytes(testClassAsPath2)).thenReturn(
testClassBytes2);
when(mockContent.getEntryAsBytes(testClassAsPath3)).thenReturn(
testClassBytes3);
final TestBundleClassLoader bundleClassLoader = createBundleClassLoader(
TestBundleClassLoader.class, bundleWiring);
assertNotNull(bundleClassLoader);
Field m_classLoader = bundleWiring.getClass().getDeclaredField("m_classLoader");
m_classLoader.setAccessible(true);
m_classLoader.set(bundleWiring, bundleClassLoader);
assertTrue(bundleClassLoader.isParallel());
final AtomicInteger loaded = new AtomicInteger();
new Thread() {
public void run() {
try
{
loaded.set(bundleClassLoader.findClass(testClassName2) != null ? 1 : 2);
}
catch (Exception e)
{
e.printStackTrace();
loaded.set(3);
}
}
}.start();
while (bundleClassLoader.m_gate.getQueueLength() == 0)
{
Thread.sleep(1);
}
final AtomicInteger loaded2 = new AtomicInteger();
new Thread() {
public void run() {
try
{
loaded2.set(bundleClassLoader.findClass(testClassName3) != null ? 1 : 2);
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
loaded2.set(3);
}
}
}.start();
while (loaded2.get() == 0)
{
Thread.sleep(1);
}
assertEquals(0, loaded.get());
assertEquals(1, bundleClassLoader.m_gate.getQueueLength());
loaded2.set(0);
Thread tester = new Thread() {
public void run() {
try
{
loaded2.set(bundleClassLoader.findClass(testClassName2) != null ? 1 : 2);
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
loaded2.set(3);
}
}
};
tester.start();
Thread.sleep(100);
assertEquals(0, loaded2.get());
assertEquals(1, bundleClassLoader.m_gate.getQueueLength());
bundleClassLoader.m_gate.release();
while (loaded.get() == 0)
{
Thread.sleep(1);
}
assertEquals(1, loaded.get());
while (loaded2.get() == 0)
{
Thread.sleep(1);
}
assertEquals(1, loaded2.get());
}
@Test
public void testClassloadStress() throws Exception
{
ExecutorService executors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 4);
final List<Throwable> exceptionsNP = Collections.synchronizedList(new ArrayList<Throwable>());
final List<Throwable> exceptionsP = Collections.synchronizedList(new ArrayList<Throwable>());
for (int i = 0; i < 100; i++) {
executors.submit(i % 2 == 0 ? new Runnable()
{
@Override
public void run()
{
try
{
testNotParallelClassload();
}
catch (Throwable e)
{
exceptionsNP.add(e);
}
}
} : new Runnable()
{
@Override
public void run()
{
try
{
testParallelClassload();
}
catch (Throwable e)
{
exceptionsP.add(e);
}
}
});
}
executors.shutdown();
executors.awaitTermination(10, TimeUnit.MINUTES);
assertTrue(exceptionsNP.toString(), exceptionsNP.isEmpty());
assertTrue(exceptionsP.toString(), exceptionsP.isEmpty());
}
@Test
public void testNotParallelClassload() throws Exception
{
Felix mockFramework = mock(Felix.class);
HookRegistry hReg = mock(HookRegistry.class);
Mockito.when(mockFramework.getHookRegistry()).thenReturn(hReg);
Content mockContent = mock(Content.class);
final Class testClass = TestClassSuper.class;
final String testClassName = testClass.getName();
final String testClassAsPath = testClassName.replace('.', '/') + ".class";
byte[] testClassBytes = createTestClassBytes(testClass, testClassAsPath);
final Class testClass2 = TestClassChild.class;
final String testClassName2 = testClass2.getName();
final String testClassAsPath2 = testClassName2.replace('.', '/') + ".class";
byte[] testClassBytes2 = createTestClassBytes(testClass2, testClassAsPath2);
final Class testClass3 = TestClass.class;
final String testClassName3 = testClass3.getName();
final String testClassAsPath3 = testClassName3.replace('.', '/') + ".class";
byte[] testClassBytes3 = createTestClassBytes(testClass3, testClassAsPath3);
List<Content> contentPath = new ArrayList<Content>();
contentPath.add(mockContent);
BundleWiringImpl bundleWiring;
StatefulResolver mockResolver;
BundleRevisionImpl mockRevisionImpl;
BundleImpl mockBundle;
mockResolver = mock(StatefulResolver.class);
mockRevisionImpl = mock(BundleRevisionImpl.class);
mockBundle = mock(BundleImpl.class);
Logger logger = new Logger();
Map configMap = new HashMap();
List<BundleRevision> fragments = new ArrayList<BundleRevision>();
List<BundleWire> wires = new ArrayList<BundleWire>();
Map<String, BundleRevision> importedPkgs = new HashMap<String, BundleRevision>();
Map<String, List<BundleRevision>> requiredPkgs = new HashMap<String, List<BundleRevision>>();
when(mockRevisionImpl.getBundle()).thenReturn(mockBundle);
when(mockBundle.getBundleId()).thenReturn(Long.valueOf(1));
bundleWiring = new BundleWiringImpl(logger, configMap, mockResolver,
mockRevisionImpl, fragments, wires, importedPkgs, requiredPkgs);
when(mockBundle.getFramework()).thenReturn(mockFramework);
when(mockFramework.getBootPackages()).thenReturn(new String[0]);
when(mockRevisionImpl.getContentPath()).thenReturn(contentPath);
when(mockContent.getEntryAsBytes(testClassAsPath)).thenReturn(
testClassBytes);
when(mockContent.getEntryAsBytes(testClassAsPath2)).thenReturn(
testClassBytes2);
when(mockContent.getEntryAsBytes(testClassAsPath3)).thenReturn(
testClassBytes3);
final TestBundleClassLoader2 bundleClassLoader = createBundleClassLoader(
TestBundleClassLoader2.class, bundleWiring);
assertNotNull(bundleClassLoader);
Field m_classLoader = bundleWiring.getClass().getDeclaredField("m_classLoader");
m_classLoader.setAccessible(true);
m_classLoader.set(bundleWiring, bundleClassLoader);
assertFalse(bundleClassLoader.isParallel());
final AtomicInteger loaded = new AtomicInteger();
new Thread() {
public void run() {
try
{
loaded.set(bundleClassLoader.findClass(testClassName2) != null ? 1 : 2);
}
catch (Exception e)
{
e.printStackTrace();
loaded.set(3);
}
}
}.start();
while (bundleClassLoader.m_gate.getQueueLength() == 0)
{
Thread.sleep(1);
}
final AtomicInteger loaded2 = new AtomicInteger();
new Thread() {
public void run() {
try
{
loaded2.set(bundleClassLoader.findClass(testClassName3) != null ? 1 : 2);
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
loaded2.set(3);
}
}
}.start();
Thread.sleep(100);
assertEquals(0, loaded.get());
assertEquals(0, loaded2.get());
assertEquals(1, bundleClassLoader.m_gate.getQueueLength());
final AtomicInteger loaded3 = new AtomicInteger();
Thread tester = new Thread() {
public void run() {
try
{
loaded3.set(bundleClassLoader.findClass(testClassName2) != null ? 1 : 2);
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
loaded3.set(3);
}
}
};
tester.start();
Thread.sleep(100);
assertEquals(0, loaded3.get());
assertEquals(0, loaded2.get());
assertEquals(0, loaded.get());
assertEquals(1, bundleClassLoader.m_gate.getQueueLength());
bundleClassLoader.m_gate.release();
while (loaded.get() == 0)
{
Thread.sleep(1);
}
assertEquals(1, loaded.get());
while (loaded2.get() == 0)
{
Thread.sleep(1);
}
assertEquals(1, loaded2.get());
while (loaded3.get() == 0)
{
Thread.sleep(1);
}
assertEquals(1, loaded3.get());
}
private static class TestBundleClassLoader extends BundleClassLoader
{
static {
ClassLoader.registerAsParallelCapable();
}
Semaphore m_gate = new Semaphore(0);
public TestBundleClassLoader(BundleWiringImpl wiring, ClassLoader parent, Logger logger)
{
super(wiring, parent, logger);
}
@Override
protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException
{
if (name.startsWith("java"))
{
return getClass().getClassLoader().loadClass(name);
}
return super.loadClass(name, resolve);
}
@Override
protected Class findClass(String name) throws ClassNotFoundException
{
if (name.startsWith("java"))
{
return getClass().getClassLoader().loadClass(name);
}
if (name.equals(TestClassSuper.class.getName()))
{
m_gate.acquireUninterruptibly();
}
return super.findClass(name);
}
}
private static class TestBundleClassLoader2 extends BundleClassLoader
{
Semaphore m_gate = new Semaphore(0);
public TestBundleClassLoader2(BundleWiringImpl wiring, ClassLoader parent, Logger logger)
{
super(wiring, parent, logger);
}
@Override
protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException
{
if (name.startsWith("java"))
{
return getClass().getClassLoader().loadClass(name);
}
return super.loadClass(name, resolve);
}
@Override
protected Class findClass(String name) throws ClassNotFoundException
{
if (name.startsWith("java"))
{
return getClass().getClassLoader().loadClass(name);
}
if (name.equals(TestClassSuper.class.getName()))
{
m_gate.acquireUninterruptibly();
}
return super.findClass(name);
}
@Override
protected boolean isParallel()
{
return false;
}
}
@SuppressWarnings("rawtypes")
private byte[] createTestClassBytes(Class testClass, String testClassAsPath)
throws IOException
{
InputStream testClassResourceStream = testClass.getClassLoader()
.getResourceAsStream(testClassAsPath);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int curByte;
while ((curByte = testClassResourceStream.read()) != -1)
{
baos.write(curByte);
}
byte[] testClassBytes = baos.toByteArray();
return testClassBytes;
}
@SuppressWarnings("rawtypes")
private <T> T createBundleClassLoader(
Class<T> bundleClassLoaderClass, BundleWiringImpl bundleWiring)
throws Exception
{
Logger logger = new Logger();
Constructor ctor = BundleRevisionImpl.getSecureAction().getConstructor(
bundleClassLoaderClass,
new Class[] { BundleWiringImpl.class, ClassLoader.class,
Logger.class });
BundleRevisionImpl.getSecureAction().setAccesssible(ctor);
T bundleClassLoader = (T) BundleRevisionImpl
.getSecureAction().invoke(
ctor,
new Object[] { bundleWiring,
this.getClass().getClassLoader(), logger });
return bundleClassLoader;
}
class TestClass
{
// An empty test class to weave.
}
class TestClassSuper
{
// An empty test class to weave.
}
class TestClassChild extends TestClassSuper
{
}
class GoodDummyWovenHook implements WeavingHook
{
// Adds the awesomePublicField to a class
@Override
@SuppressWarnings("unchecked")
public void weave(WovenClass wovenClass)
{
byte[] wovenClassBytes = wovenClass.getBytes();
ClassNode classNode = new ClassNode();
ClassReader reader = new ClassReader(wovenClassBytes);
reader.accept(classNode, 0);
classNode.fields.add(new FieldNode(Opcodes.ACC_PUBLIC,
"awesomePublicField", "Ljava/lang/String;", null, null));
ClassWriter writer = new ClassWriter(reader, Opcodes.ASM4);
classNode.accept(writer);
wovenClass.setBytes(writer.toByteArray());
}
}
class BadDefineWovenHook implements WeavingHook
{
// Adds the awesomePublicField twice to the class. This is bad java.
@Override
@SuppressWarnings("unchecked")
public void weave(WovenClass wovenClass)
{
byte[] wovenClassBytes = wovenClass.getBytes();
ClassNode classNode = new ClassNode();
ClassReader reader = new ClassReader(wovenClassBytes);
reader.accept(classNode, 0);
classNode.fields.add(new FieldNode(Opcodes.ACC_PUBLIC,
"awesomePublicField", "Ljava/lang/String;", null, null));
classNode.fields.add(new FieldNode(Opcodes.ACC_PUBLIC,
"awesomePublicField", "Ljava/lang/String;", null, null));
ClassWriter writer = new ClassWriter(reader, Opcodes.ASM4);
classNode.accept(writer);
wovenClass.setBytes(writer.toByteArray());
}
}
class BadDummyWovenHook implements WeavingHook
{
// Just Blow up
@Override
public void weave(WovenClass wovenClass)
{
throw new WeavingException("Bad Weaver!");
}
}
class DummyWovenClassListener implements WovenClassListener
{
public List<Integer> stateList = new ArrayList<Integer>();
@Override
public void modified(WovenClass wovenClass)
{
stateList.add(wovenClass.getState());
}
}
}