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());

+		}

+	}

 }