FELIX-4582 Added Woven Class Listeners functionality from OSGi R6 Chapter 56. Added additional tests for listeners.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1641308 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java b/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java
index 9bb5ee1..2028c30 100644
--- a/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/BundleWiringImpl.java
@@ -64,6 +64,8 @@
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.namespace.IdentityNamespace;
import org.osgi.framework.wiring.BundleCapability;
import org.osgi.framework.wiring.BundleRequirement;
@@ -2003,7 +2005,7 @@
catch (ClassNotFoundException cnfe)
{
ClassNotFoundException ex = cnfe;
- if (m_wiring.m_logger.getLogLevel() >= Logger.LOG_DEBUG)
+ if (m_logger.getLogLevel() >= Logger.LOG_DEBUG)
{
String msg = diagnoseClassLoadError(m_wiring.m_resolver, m_wiring.m_revision, name);
ex = (msg != null)
@@ -2075,56 +2077,27 @@
Felix felix = ((BundleImpl) m_wiring.m_revision.getBundle()).getFramework();
Set<ServiceReference<WeavingHook>> hooks =
felix.getHooks(WeavingHook.class);
+
+ Set<ServiceReference<WovenClassListener>> wovenClassListeners =
+ felix.getHooks(WovenClassListener.class);
+
WovenClassImpl wci = null;
- if (!hooks.isEmpty())
- {
- // Create woven class to be used for hooks.
- wci = new WovenClassImpl(name, m_wiring, bytes);
- // Loop through hooks in service ranking order.
- for (ServiceReference<WeavingHook> sr : hooks)
- {
- // Only use the hook if it is not black listed.
- if (!felix.isHookBlackListed(sr))
- {
- // Get the hook service object.
- // Note that we don't use the bundle context
- // to get the service object since that would
- // perform sercurity checks.
- WeavingHook wh = felix.getService(felix, sr, false);
- if (wh != null)
- {
- try
- {
- BundleRevisionImpl.getSecureAction()
- .invokeWeavingHook(wh, wci);
- }
- catch (Throwable th)
- {
- if (!(th instanceof WeavingException))
- {
- felix.blackListHook(sr);
- }
- felix.fireFrameworkEvent(
- FrameworkEvent.ERROR,
- sr.getBundle(),
- th);
-
- // Mark the woven class as incomplete.
- wci.complete(null, null, null);
- // Throw class format exception per spec.
- Error error = new ClassFormatError("Weaving hook failed.");
- error.initCause(th);
- throw error;
- }
- finally
- {
- felix.ungetService(felix, sr, null);
- }
- }
- }
- }
- }
-
+ if (!hooks.isEmpty())
+ {
+ // Create woven class to be used for hooks.
+ wci = new WovenClassImpl(name, m_wiring, bytes);
+ try
+ {
+ transformClass(felix, wci, hooks, wovenClassListeners,
+ name, bytes);
+ }
+ catch (Error e)
+ {
+ wci.setState(WovenClass.TRANSFORMING_FAILED);
+ callWovenClassListeners(felix, wovenClassListeners, wci);
+ throw e;
+ }
+ }
// Before we actually attempt to define the class, grab
// the lock for this class loader and make sure than no
// other thread has defined this class in the meantime.
@@ -2153,207 +2126,19 @@
}
}
- byte[] wovenBytes = null;
- Class wovenClass = null;
- List<String> wovenImports = null;
- try
+ try
{
- if (clazz == null)
- {
- // If we have a woven class then get the class bytes from
- // it since they may have changed.
- // NOTE: We are taking a snapshot of these values and
- // are not preventing a malbehaving weaving hook from
- // modifying them after the fact. The price of preventing
- // this isn't worth it, since they can already wreck
- // havoc via weaving anyway. However, we do pass the
- // snapshot values into the woven class when we mark it
- // as complete so that it will refect the actual values
- // we used to define the class.
- if (wci != null)
- {
- bytes = wovenBytes = wci._getBytes();
- wovenImports = wci.getDynamicImportsInternal();
-
- // Try to add any woven dynamic imports, since they
- // could potentially be needed when defining the class.
- List<BundleRequirement> allWovenReqs =
- new ArrayList<BundleRequirement>();
- for (String s : wovenImports)
- {
- try
- {
- List<BundleRequirement> wovenReqs =
- ManifestParser.parseDynamicImportHeader(
- m_wiring.m_logger, m_wiring.m_revision, s);
- allWovenReqs.addAll(wovenReqs);
- }
- catch (BundleException ex)
- {
- // There should be no exception here
- // since we checked syntax before adding
- // dynamic import strings to list.
- }
- }
- // Add the dynamic requirements.
- if (!allWovenReqs.isEmpty())
- {
- // Check for duplicate woven imports.
- // First grab existing woven imports, if any.
- Set<String> filters = new HashSet<String>();
- if (m_wiring.m_wovenReqs != null)
- {
- for (BundleRequirement req : m_wiring.m_wovenReqs)
- {
- filters.add(
- ((BundleRequirementImpl) req)
- .getFilter().toString());
- }
- }
- // Then check new woven imports for duplicates
- // against existing and self.
- int idx = allWovenReqs.size();
- while (idx < allWovenReqs.size())
- {
- BundleRequirement wovenReq = allWovenReqs.get(idx);
- String filter = ((BundleRequirementImpl)
- wovenReq).getFilter().toString();
- if (!filters.contains(filter))
- {
- filters.add(filter);
- idx++;
- }
- else
- {
- allWovenReqs.remove(idx);
- }
- }
- // Merge existing with new imports, if any.
- if (!allWovenReqs.isEmpty())
- {
- if (m_wiring.m_wovenReqs != null)
- {
- allWovenReqs.addAll(0, m_wiring.m_wovenReqs);
- }
- m_wiring.m_wovenReqs = allWovenReqs;
- }
- }
- }
-
- int activationPolicy =
- ((BundleImpl) getBundle()).isDeclaredActivationPolicyUsed()
- ? ((BundleRevisionImpl) getBundle()
- .adapt(BundleRevision.class)).getDeclaredActivationPolicy()
- : EAGER_ACTIVATION;
-
- // If the revision is using deferred activation, then if
- // we load this class from this revision we need to activate
- // the bundle before returning the class. We will short
- // circuit the trigger matching if the trigger is already
- // tripped.
- boolean isTriggerClass = m_isActivationTriggered
- ? false : m_wiring.m_revision.isActivationTrigger(pkgName);
- if (!m_isActivationTriggered
- && isTriggerClass
- && (activationPolicy == BundleRevisionImpl.LAZY_ACTIVATION)
- && (getBundle().getState() == Bundle.STARTING))
- {
- List deferredList = (List) m_deferredActivation.get();
- if (deferredList == null)
- {
- deferredList = new ArrayList();
- m_deferredActivation.set(deferredList);
- }
- deferredList.add(new Object[] { name, getBundle() });
- }
- // We need to try to define a Package object for the class
- // before we call defineClass() if we haven't already
- // created it.
- if (pkgName.length() > 0)
- {
- if (getPackage(pkgName) == null)
- {
- Object[] params = definePackage(pkgName);
-
- // This is a harmless check-then-act situation,
- // where threads might be racing to create different
- // classes in the same package, so catch and ignore
- // any IAEs that may occur.
- try
- {
- definePackage(
- pkgName,
- (String) params[0],
- (String) params[1],
- (String) params[2],
- (String) params[3],
- (String) params[4],
- (String) params[5],
- null);
- }
- catch (IllegalArgumentException ex)
- {
- // Ignore.
- }
- }
- }
-
- // If we can load the class from a dex file do so
- if (content instanceof JarContent)
- {
- try
- {
- clazz = getDexFileClass((JarContent) content, name, this);
- }
- catch (Exception ex)
- {
- // Looks like we can't
- }
- }
-
- if (clazz == null)
- {
- // If we have a security context, then use it to
- // define the class with it for security purposes,
- // otherwise define the class without a protection domain.
- if (m_wiring.m_revision.getProtectionDomain() != null)
- {
- clazz = defineClass(name, bytes, 0, bytes.length,
- m_wiring.m_revision.getProtectionDomain());
- }
- else
- {
- clazz = defineClass(name, bytes, 0, bytes.length);
- }
-
- wovenClass = clazz;
- }
-
- // At this point if we have a trigger class, then the deferred
- // activation trigger has tripped.
- if (!m_isActivationTriggered && isTriggerClass && (clazz != null))
- {
- m_isActivationTriggered = true;
- }
- }
- }
- finally
+ clazz = defineClass(felix, wovenClassListeners, wci, name,
+ clazz, bytes, content, pkgName, lock);
+ }
+ catch (ClassFormatError e)
{
- // If we have a woven class, mark it as complete.
- // Not exactly clear how we should deal with the
- // case where the weaving didn't happen because
- // someone else beat us in defining the class.
- if (wci != null)
- {
- wci.complete(wovenClass, wovenBytes, wovenImports);
- }
-
- synchronized (lock)
- {
- m_classLocks.remove(name);
- lock.notifyAll();
- }
- }
+ if(wci != null)
+ {
+ wci.setState(WovenClass.DEFINE_FAILED);
+ callWovenClassListeners(felix, wovenClassListeners, wci);
+ }
+ }
// Perform deferred activation without holding the class loader lock,
// if the class we are returning is the instigating class.
@@ -2376,7 +2161,7 @@
}
catch (Throwable ex)
{
- m_wiring.m_logger.log((BundleImpl) (lazy)[1],
+ m_logger.log((BundleImpl) (lazy)[1],
Logger.LOG_WARNING,
"Unable to lazily start bundle.",
ex);
@@ -2389,6 +2174,293 @@
return clazz;
}
+ protected Class defineClass(Felix felix,
+ Set<ServiceReference<WovenClassListener>> wovenClassListeners,
+ WovenClassImpl wci, String name, Class clazz, byte[] bytes,
+ Content content, String pkgName, Object lock)
+ throws ClassFormatError
+ {
+
+ try
+ {
+ if (clazz == null)
+ {
+ // If we have a woven class then get the class bytes from
+ // it since they may have changed.
+ // NOTE: We are taking a snapshot of these values and
+ // are not preventing a malbehaving weaving hook from
+ // modifying them after the fact. The price of preventing
+ // this isn't worth it, since they can already wreck
+ // havoc via weaving anyway. However, we do pass the
+ // snapshot values into the woven class when we mark it
+ // as complete so that it will refect the actual values
+ // we used to define the class.
+ if (wci != null)
+ {
+ bytes = wci._getBytes();
+ List<String> wovenImports = wci.getDynamicImportsInternal();
+
+ // Try to add any woven dynamic imports, since they
+ // could potentially be needed when defining the class.
+ List<BundleRequirement> allWovenReqs =
+ new ArrayList<BundleRequirement>();
+ for (String s : wovenImports)
+ {
+ try
+ {
+ List<BundleRequirement> wovenReqs =
+ ManifestParser.parseDynamicImportHeader(
+ m_logger, m_wiring.m_revision, s);
+ allWovenReqs.addAll(wovenReqs);
+ }
+ catch (BundleException ex)
+ {
+ // There should be no exception here
+ // since we checked syntax before adding
+ // dynamic import strings to list.
+ }
+ }
+ // Add the dynamic requirements.
+ if (!allWovenReqs.isEmpty())
+ {
+ // Check for duplicate woven imports.
+ // First grab existing woven imports, if any.
+ Set<String> filters = new HashSet<String>();
+ if (m_wiring.m_wovenReqs != null)
+ {
+ for (BundleRequirement req : m_wiring.m_wovenReqs)
+ {
+ filters.add(
+ ((BundleRequirementImpl) req)
+ .getFilter().toString());
+ }
+ }
+ // Then check new woven imports for duplicates
+ // against existing and self.
+ int idx = allWovenReqs.size();
+ while (idx < allWovenReqs.size())
+ {
+ BundleRequirement wovenReq = allWovenReqs.get(idx);
+ String filter = ((BundleRequirementImpl)
+ wovenReq).getFilter().toString();
+ if (!filters.contains(filter))
+ {
+ filters.add(filter);
+ idx++;
+ }
+ else
+ {
+ allWovenReqs.remove(idx);
+ }
+ }
+ // Merge existing with new imports, if any.
+ if (!allWovenReqs.isEmpty())
+ {
+ if (m_wiring.m_wovenReqs != null)
+ {
+ allWovenReqs.addAll(0, m_wiring.m_wovenReqs);
+ }
+ m_wiring.m_wovenReqs = allWovenReqs;
+ }
+ }
+ }
+
+ int activationPolicy =
+ ((BundleImpl) getBundle()).isDeclaredActivationPolicyUsed()
+ ? ((BundleRevisionImpl) getBundle()
+ .adapt(BundleRevision.class)).getDeclaredActivationPolicy()
+ : EAGER_ACTIVATION;
+
+ // If the revision is using deferred activation, then if
+ // we load this class from this revision we need to activate
+ // the bundle before returning the class. We will short
+ // circuit the trigger matching if the trigger is already
+ // tripped.
+ boolean isTriggerClass = m_isActivationTriggered
+ ? false : m_wiring.m_revision.isActivationTrigger(pkgName);
+ if (!m_isActivationTriggered
+ && isTriggerClass
+ && (activationPolicy == BundleRevisionImpl.LAZY_ACTIVATION)
+ && (getBundle().getState() == Bundle.STARTING))
+ {
+ List deferredList = (List) m_deferredActivation.get();
+ if (deferredList == null)
+ {
+ deferredList = new ArrayList();
+ m_deferredActivation.set(deferredList);
+ }
+ deferredList.add(new Object[] { name, getBundle() });
+ }
+ // We need to try to define a Package object for the class
+ // before we call defineClass() if we haven't already
+ // created it.
+ if (pkgName.length() > 0)
+ {
+ if (getPackage(pkgName) == null)
+ {
+ Object[] params = definePackage(pkgName);
+
+ // This is a harmless check-then-act situation,
+ // where threads might be racing to create different
+ // classes in the same package, so catch and ignore
+ // any IAEs that may occur.
+ try
+ {
+ definePackage(
+ pkgName,
+ (String) params[0],
+ (String) params[1],
+ (String) params[2],
+ (String) params[3],
+ (String) params[4],
+ (String) params[5],
+ null);
+ }
+ catch (IllegalArgumentException ex)
+ {
+ // Ignore.
+ }
+ }
+ }
+
+ // If we can load the class from a dex file do so
+ if (content instanceof JarContent)
+ {
+ try
+ {
+ clazz = getDexFileClass((JarContent) content, name, this);
+ }
+ catch (Exception ex)
+ {
+ // Looks like we can't
+ }
+ }
+
+ if (clazz == null)
+ {
+ // If we have a security context, then use it to
+ // define the class with it for security purposes,
+ // otherwise define the class without a protection domain.
+ if (m_wiring.m_revision.getProtectionDomain() != null)
+ {
+ clazz = defineClass(name, bytes, 0, bytes.length,
+ m_wiring.m_revision.getProtectionDomain());
+ }
+ else
+ {
+ clazz = defineClass(name, bytes, 0, bytes.length);
+ }
+ if(wci != null)
+ {
+ wci.setState(WovenClass.DEFINED);
+ callWovenClassListeners(felix, wovenClassListeners, wci);
+ }
+
+ }
+
+ // At this point if we have a trigger class, then the deferred
+ // activation trigger has tripped.
+ if (!m_isActivationTriggered && isTriggerClass && (clazz != null))
+ {
+ m_isActivationTriggered = true;
+ }
+ }
+ }
+ finally
+ {
+ // If we have a woven class, mark it as complete.
+ // Not exactly clear how we should deal with the
+ // case where the weaving didn't happen because
+ // someone else beat us in defining the class.
+ if (wci != null)
+ {
+ wci.complete(clazz, bytes, wci.getDynamicImportsInternal());
+ }
+
+ synchronized (lock)
+ {
+ m_classLocks.remove(name);
+ lock.notifyAll();
+ }
+ }
+ return clazz;
+ }
+
+ protected void transformClass(Felix felix, WovenClassImpl wci,
+ Set<ServiceReference<WeavingHook>> hooks,
+ Set<ServiceReference<WovenClassListener>> wovenClassListeners,
+ String name, byte[] bytes) throws Error {
+
+ // Loop through hooks in service ranking order.
+ for (ServiceReference<WeavingHook> sr : hooks)
+ {
+ // Only use the hook if it is not black listed.
+ if (!felix.isHookBlackListed(sr))
+ {
+ // Get the hook service object.
+ // Note that we don't use the bundle context
+ // to get the service object since that would
+ // perform sercurity checks.
+ WeavingHook wh = felix.getService(felix, sr, false);
+ if (wh != null)
+ {
+ try
+ {
+ BundleRevisionImpl.getSecureAction()
+ .invokeWeavingHook(wh, wci);
+ }
+ catch (Throwable th)
+ {
+ if (!(th instanceof WeavingException))
+ {
+ felix.blackListHook(sr);
+ }
+ felix.fireFrameworkEvent(
+ FrameworkEvent.ERROR,
+ sr.getBundle(),
+ th);
+
+ // Mark the woven class as incomplete.
+ wci.complete(null, null, null);
+ // Throw class format exception per spec.
+ Error error = new ClassFormatError("Weaving hook failed.");
+ error.initCause(th);
+ throw error;
+ }
+ finally
+ {
+ felix.ungetService(felix, sr, null);
+ }
+ }
+ }
+ }
+ wci.setState(WovenClass.TRANSFORMED);
+ callWovenClassListeners(felix, wovenClassListeners, wci);
+ }
+
+ protected void callWovenClassListeners(Felix felix, Set<ServiceReference<WovenClassListener>> wovenClassListeners, WovenClass wovenClass)
+ {
+ if(wovenClassListeners != null)
+ {
+ for(ServiceReference<WovenClassListener> currentWovenClassListenerRef : wovenClassListeners)
+ {
+ WovenClassListener currentWovenClassListner = felix.getService(felix, currentWovenClassListenerRef, false);
+ try
+ {
+ BundleRevisionImpl.getSecureAction().invokeWovenClassListener(currentWovenClassListner, wovenClass);
+ }
+ catch (Exception e)
+ {
+ m_logger.log(Logger.LOG_ERROR, "Woven Class Listner failed.", e);
+ }
+ finally
+ {
+ felix.ungetService(felix, currentWovenClassListenerRef, null);
+ }
+ }
+ }
+ }
+
private Object[] definePackage(String pkgName)
{
String spectitle = (String) m_wiring.m_revision.getHeaders().get("Specification-Title");
diff --git a/framework/src/main/java/org/apache/felix/framework/WovenClassImpl.java b/framework/src/main/java/org/apache/felix/framework/WovenClassImpl.java
index e563935..e9f1c74 100644
--- a/framework/src/main/java/org/apache/felix/framework/WovenClassImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/WovenClassImpl.java
@@ -40,17 +40,18 @@
private List<String> m_imports = new ArrayList<String>();
private Class m_definedClass = null;
private boolean m_isComplete = false;
+ private int m_state;
/* package */ WovenClassImpl(String className, BundleWiring wiring, byte[] bytes)
{
m_className = className;
m_wiring = wiring;
m_bytes = bytes;
+ m_state = TRANSFORMING;
}
synchronized void complete(Class definedClass, byte[] bytes, List<String> imports)
{
- m_isComplete = true;
m_definedClass = definedClass;
m_bytes = (bytes == null) ? m_bytes : bytes;
m_imports = (imports == null)
@@ -104,7 +105,7 @@
public synchronized boolean isWeavingComplete()
{
- return m_isComplete;
+ return m_isComplete;
}
public String getClassName()
@@ -378,8 +379,21 @@
/* (non-Javadoc)
* @see org.osgi.framework.hooks.weaving.WovenClass#getState()
*/
- public int getState() {
- throw new UnsupportedOperationException(); // TODO
+ public synchronized int getState()
+ {
+ return m_state;
+ }
+
+ public synchronized void setState(int state)
+ {
+ //Per 56.6.4.13 Weaving complete if state is DEFINED, DEFINE_FAILED, or TRANSFORMING_FAILED
+ if(!m_isComplete && (state == DEFINED ||
+ state == DEFINE_FAILED ||
+ state == TRANSFORMING_FAILED))
+ {
+ m_isComplete = true;
+ }
+ m_state = state;
}
}
\ No newline at end of file
diff --git a/framework/src/test/java/org/apache/felix/framework/BundleWiringImplTest.java b/framework/src/test/java/org/apache/felix/framework/BundleWiringImplTest.java
index 72e5015..c39714c 100644
--- a/framework/src/test/java/org/apache/felix/framework/BundleWiringImplTest.java
+++ b/framework/src/test/java/org/apache/felix/framework/BundleWiringImplTest.java
@@ -45,6 +45,7 @@
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;
@@ -107,7 +108,8 @@
BundleClassLoader bundleClassLoader = createBundleClassLoader(BundleClassLoaderJava5.class, bundleWiring);
assertNotNull(bundleClassLoader);
Class foundClass = null;
- try {
+ try
+ {
foundClass = bundleClassLoader.findClass("org.apache.felix.test.NonExistant");
}
catch (ClassNotFoundException e)
@@ -141,7 +143,8 @@
BundleClassLoader bundleClassLoader = createBundleClassLoader(BundleClassLoaderJava5.class, bundleWiring);
assertNotNull(bundleClassLoader);
Class foundClass = null;
- try {
+ try
+ {
foundClass = bundleClassLoader.findClass(TestClass.class.getName());
}
@@ -159,10 +162,16 @@
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";
@@ -181,10 +190,14 @@
when(mockFramework.getHooks(WeavingHook.class)).thenReturn(hooks);
when(mockFramework.getService(mockFramework, mockServiceReferenceWeavingHook, false)).thenReturn(new GoodDummyWovenHook());
+ when(mockFramework.getHooks(WovenClassListener.class)).thenReturn(listeners);
+ when(mockFramework.getService(mockFramework, mockServiceReferenceWovenClassListener, false)).thenReturn(dummyWovenClassListener);
+
BundleClassLoader bundleClassLoader = createBundleClassLoader(BundleClassLoaderJava5.class, bundleWiring);
assertNotNull(bundleClassLoader);
Class foundClass = null;
- try {
+ try
+ {
foundClass = bundleClassLoader.findClass(TestClass.class.getName());
}
@@ -194,6 +207,9 @@
}
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", WovenClass.TRANSFORMED, dummyWovenClassListener.stateList.get(0));
+ assertEquals("The second state change should define the class", WovenClass.DEFINED, dummyWovenClassListener.stateList.get(1));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@@ -203,9 +219,15 @@
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();
@@ -225,6 +247,9 @@
when(mockFramework.getHooks(WeavingHook.class)).thenReturn(hooks);
when(mockFramework.getService(mockFramework, mockServiceReferenceWeavingHook, false)).thenReturn(new BadDummyWovenHook());
+ when(mockFramework.getHooks(WovenClassListener.class)).thenReturn(listeners);
+ when(mockFramework.getService(mockFramework, mockServiceReferenceWovenClassListener, false)).thenReturn(dummyWovenClassListener);
+
BundleClassLoader bundleClassLoader = createBundleClassLoader(BundleClassLoaderJava5.class, bundleWiring);
assertNotNull(bundleClassLoader);
@@ -238,8 +263,65 @@
//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", 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);
+
+ when(mockFramework.getHooks(WeavingHook.class)).thenReturn(hooks);
+ when(mockFramework.getService(mockFramework, mockServiceReferenceWeavingHook, false)).thenReturn(new BadDefineWovenHook());
+
+ when(mockFramework.getHooks(WovenClassListener.class)).thenReturn(listeners);
+ when(mockFramework.getService(mockFramework, mockServiceReferenceWovenClassListener, false)).thenReturn(dummyWovenClassListener);
+
+ BundleClassLoader bundleClassLoader = createBundleClassLoader(BundleClassLoaderJava5.class, bundleWiring);
+ assertNotNull(bundleClassLoader);
+ try
+ {
+
+ bundleClassLoader.findClass(TestClass.class.getName());
+ }
+ catch (ClassNotFoundException e)
+ {
+ fail("Class should not throw exception");
+ }
+ assertEquals("There should be 2 state changes fired by the weaving", 2, dummyWovenClassListener.stateList.size());
+ assertEquals("The first state change should transform the class", WovenClass.TRANSFORMED, dummyWovenClassListener.stateList.get(0));
+ assertEquals("The second state change failed the define on the class", WovenClass.DEFINE_FAILED, dummyWovenClassListener.stateList.get(1));
+ }
+
@SuppressWarnings("rawtypes")
private byte[] createTestClassBytes(Class testClass, String testClassAsPath)
throws IOException
@@ -294,6 +376,25 @@
}
}
+ class BadDefineWovenHook implements WeavingHook {
+ //Adds the awesomePublicField twice to the class. This is bad java.
+ @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
@@ -302,4 +403,14 @@
throw new WeavingException("Bad Weaver!");
}
}
+
+ class DummyWovenClassListener implements WovenClassListener
+ {
+ public List<Integer> stateList = new ArrayList<Integer>();
+
+ public void modified(WovenClass wovenClass)
+ {
+ stateList.add(wovenClass.getState());
+ }
+ }
}