blob: 6e01f33d78f182ffa973c8446c1d04e109dad547 [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 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());
}
}
}