/* | |
* 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 static org.junit.Assert.assertEquals; | |
import static org.junit.Assert.assertNotNull; | |
import static org.junit.Assert.assertNull; | |
import static org.junit.Assert.fail; | |
import static org.mockito.Mockito.mock; | |
import static org.mockito.Mockito.when; | |
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.lang.reflect.Constructor; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
import org.apache.felix.framework.BundleWiringImpl.BundleClassLoader; | |
import org.apache.felix.framework.cache.Content; | |
import org.junit.Test; | |
import org.mockito.Mockito; | |
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; | |
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)); | |
} | |
@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 BundleClassLoader createBundleClassLoader( | |
Class bundleClassLoaderClass, BundleWiringImpl bundleWiring) | |
throws Exception | |
{ | |
Logger logger = new Logger(); | |
Constructor ctor = BundleRevisionImpl.getSecureAction().getConstructor( | |
bundleClassLoaderClass, | |
new Class[] { BundleWiringImpl.class, ClassLoader.class, | |
Logger.class }); | |
BundleClassLoader bundleClassLoader = (BundleClassLoader) BundleRevisionImpl | |
.getSecureAction().invoke( | |
ctor, | |
new Object[] { bundleWiring, | |
this.getClass().getClassLoader(), logger }); | |
return bundleClassLoader; | |
} | |
class TestClass | |
{ | |
// An empty test class to weave. | |
} | |
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()); | |
} | |
} | |
} |