Implement new start level approach. (FELIX-2975)


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1128399 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/BundleImpl.java b/framework/src/main/java/org/apache/felix/framework/BundleImpl.java
index 3d7196b..17396fb 100644
--- a/framework/src/main/java/org/apache/felix/framework/BundleImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/BundleImpl.java
@@ -38,6 +38,7 @@
 import org.osgi.framework.ServicePermission;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.Version;
+import org.osgi.framework.startlevel.BundleStartLevel;
 import org.osgi.framework.wiring.BundleRevision;
 import org.osgi.framework.wiring.BundleWire;
 
@@ -999,9 +1000,14 @@
         getFramework().uninstallBundle(this);
     }
 
-    public <A> A adapt(Class<A> type)
+    public synchronized <A> A adapt(Class<A> type)
     {
-        throw new UnsupportedOperationException("Not supported yet.");
+        if (type == BundleStartLevel.class)
+        {
+            return (A) getFramework().adapt(FrameworkStartLevelImpl.class)
+                .createBundleStartLevel(this);
+        }
+        return null;
     }
 
     public File getDataFile(String filename)
diff --git a/framework/src/main/java/org/apache/felix/framework/Felix.java b/framework/src/main/java/org/apache/felix/framework/Felix.java
index a6e009d..1cae819 100644
--- a/framework/src/main/java/org/apache/felix/framework/Felix.java
+++ b/framework/src/main/java/org/apache/felix/framework/Felix.java
@@ -70,6 +70,7 @@
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.framework.hooks.service.FindHook;
 import org.osgi.framework.hooks.service.ListenerHook;
+import org.osgi.framework.startlevel.FrameworkStartLevel;
 import org.osgi.framework.wiring.BundleCapability;
 import org.osgi.framework.wiring.BundleRequirement;
 import org.osgi.framework.wiring.BundleRevision;
@@ -88,6 +89,7 @@
 
     // Framework wiring object.
     private final FrameworkWiringImpl m_fwkWiring;
+    private final FrameworkStartLevelImpl m_fwkStartLevel;
 
     // Logging related member variables.
     private final Logger m_logger;
@@ -391,6 +393,8 @@
 
         // Create framework wiring object.
         m_fwkWiring = new FrameworkWiringImpl(this);
+        // Create framework start level object.
+        m_fwkStartLevel = new FrameworkStartLevelImpl(this);
     }
 
     Logger getLogger()
@@ -460,10 +464,16 @@
     @Override
     public <A> A adapt(Class<A> type)
     {
-        if (type == FrameworkWiring.class)
+        if ((type == FrameworkWiring.class)
+            || (type == FrameworkWiringImpl.class))
         {
             return (A) m_fwkWiring;
         }
+        else if ((type == FrameworkStartLevel.class)
+            || (type == FrameworkStartLevelImpl.class))
+        {
+            return (A) m_fwkStartLevel;
+        }
         return super.adapt(type);
     }
 
@@ -848,7 +858,7 @@
 
                 if (sl instanceof StartLevelImpl)
                 {
-                    ((StartLevelImpl) sl).setStartLevelAndWait(startLevel);
+                    m_fwkStartLevel.setStartLevelAndWait(startLevel);
                 }
                 else
                 {
@@ -1078,7 +1088,7 @@
      * directly.
      * @param requestedLevel The new start level of the framework.
     **/
-    void setActiveStartLevel(int requestedLevel)
+    void setActiveStartLevel(int requestedLevel, FrameworkListener[] listeners)
     {
         Bundle[] bundles = null;
 
@@ -1266,6 +1276,24 @@
         if (getState() == Bundle.ACTIVE)
         {
             fireFrameworkEvent(FrameworkEvent.STARTLEVEL_CHANGED, this, null);
+
+            if (listeners != null)
+            {
+                FrameworkEvent event = new FrameworkEvent(
+                    FrameworkEvent.STARTLEVEL_CHANGED, this, null);
+                for (FrameworkListener l : listeners)
+                {
+                    try
+                    {
+                        l.frameworkEvent(event);
+                    }
+                    catch (Throwable th)
+                    {
+                        m_logger.log(Logger.LOG_ERROR,
+                            "Framework listener delivery error.", th);
+                    }
+                }
+            }
         }
     }
 
@@ -1801,7 +1829,7 @@
             // Check to see if there is a start level change in progress and if so
             // add this bundle to the bundles being processed by the start level
             // thread and return.
-            if (!Thread.currentThread().getName().equals(StartLevelImpl.THREAD_NAME))
+            if (!Thread.currentThread().getName().equals(FrameworkStartLevelImpl.THREAD_NAME))
             {
                 synchronized (m_startLevelBundles)
                 {
@@ -4848,20 +4876,12 @@
             // shutdown happens on its own thread, we can wait for the start
             // level service to finish before proceeding by calling the
             // non-spec setStartLevelAndWait() method.
-            try
-            {
-                StartLevelImpl sl = (StartLevelImpl) getService(
-                    Felix.this,
-                    getServiceReferences(Felix.this, StartLevel.class.getName(), null, true)[0]);
-                sl.setStartLevelAndWait(0);
-            }
-            catch (InvalidSyntaxException ex)
-            {
-                // Should never happen.
-            }
+            m_fwkStartLevel.setStartLevelAndWait(0);
 
             // Stop framework wiring thread.
             m_fwkWiring.stop();
+            // Stop framework start level thread.
+            m_fwkStartLevel.stop();
 
             // Shutdown event dispatching queue.
             EventDispatcher.shutdown();
diff --git a/framework/src/main/java/org/apache/felix/framework/FrameworkStartLevelImpl.java b/framework/src/main/java/org/apache/felix/framework/FrameworkStartLevelImpl.java
new file mode 100644
index 0000000..6ce7924
--- /dev/null
+++ b/framework/src/main/java/org/apache/felix/framework/FrameworkStartLevelImpl.java
@@ -0,0 +1,303 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+import org.osgi.framework.AdminPermission;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.startlevel.BundleStartLevel;
+import org.osgi.framework.startlevel.FrameworkStartLevel;
+
+class FrameworkStartLevelImpl implements FrameworkStartLevel, Runnable
+{
+    static final String THREAD_NAME = "FelixStartLevel";
+
+    private static final int BUNDLE_IDX = 0;
+    private static final int STARTLEVEL_IDX = 1;
+
+    private final Felix m_felix;
+    private final List m_requests = new ArrayList();
+    private final List<FrameworkListener[]> m_requestListeners
+        = new ArrayList<FrameworkListener[]>();
+    private Thread m_thread = null;
+
+    FrameworkStartLevelImpl(Felix felix)
+    {
+        m_felix = felix;
+    }
+
+    // Should only be called hold requestList lock.
+    private void startThread()
+    {
+        // Start a thread to perform asynchronous package refreshes.
+        if (m_thread == null)
+        {
+            m_thread = new Thread(this, THREAD_NAME);
+            m_thread.setDaemon(true);
+            m_thread.start();
+        }
+    }
+
+    /**
+     * Stops the FelixStartLevel thread on system shutdown. Shutting down the
+     * thread explicitly is required in the embedded case, where Felix may be
+     * stopped without the Java VM being stopped. In this case the
+     * FelixStartLevel thread must be stopped explicitly.
+     * <p>
+     * This method is called by the
+     * {@link StartLevelActivator#stop(BundleContext)} method.
+     */
+    void stop()
+    {
+        synchronized (m_requests)
+        {
+            if (m_thread != null)
+            {
+                // Null thread variable to signal to the thread that
+                // we want it to exit.
+                m_thread = null;
+
+                // Wake up the thread, if it is currently in the wait() state
+                // for more work.
+                m_requests.notifyAll();
+            }
+        }
+    }
+
+    public Bundle getBundle()
+    {
+        return m_felix;
+    }
+
+    public int getStartLevel()
+    {
+        return m_felix.getActiveStartLevel();
+    }
+
+    public void setStartLevel(int startlevel, FrameworkListener... listeners)
+    {
+        Object sm = System.getSecurityManager();
+
+        if (sm != null)
+        {
+            ((SecurityManager) sm).checkPermission(
+                new AdminPermission(m_felix, AdminPermission.STARTLEVEL));
+        }
+
+        if (startlevel <= 0)
+        {
+            throw new IllegalArgumentException(
+                "Start level must be greater than zero.");
+        }
+
+        synchronized (m_requests)
+        {
+            // Start thread if necessary.
+            startThread();
+            // Queue request.
+            m_requestListeners.add(listeners);
+            m_requests.add(new Integer(startlevel));
+            m_requests.notifyAll();
+        }
+    }
+
+    /**
+     * This method is currently only called by the by the thread that calls
+     * the Felix.start() method and the shutdown thread when the
+     * framework is shutting down.
+     * @param startlevel
+    **/
+    /* package */ void setStartLevelAndWait(int startlevel)
+    {
+        Object request = new Integer(startlevel);
+        synchronized (request)
+        {
+            synchronized (m_requests)
+            {
+                // Start thread if necessary.
+                startThread();
+                // Queue request.
+                m_requestListeners.add(null);
+                m_requests.add(request);
+                m_requests.notifyAll();
+            }
+
+            try
+            {
+                request.wait();
+            }
+            catch (InterruptedException ex)
+            {
+                // Log it and ignore since it won't cause much of an issue.
+                m_felix.getLogger().log(
+                    Logger.LOG_WARNING,
+                    "Wait for start level change during shutdown interrupted.",
+                    ex);
+            }
+        }
+    }
+
+    public int getInitialBundleStartLevel()
+    {
+        return m_felix.getInitialBundleStartLevel();
+    }
+
+    public void setInitialBundleStartLevel(int startlevel)
+    {
+        Object sm = System.getSecurityManager();
+
+        if (sm != null)
+        {
+            ((SecurityManager) sm).checkPermission(
+                new AdminPermission(m_felix, AdminPermission.STARTLEVEL));
+        }
+        m_felix.setInitialBundleStartLevel(startlevel);
+    }
+
+    BundleStartLevel createBundleStartLevel(BundleImpl bundle)
+    {
+        return new BundleStartLevelImpl(bundle);
+    }
+
+    class BundleStartLevelImpl implements BundleStartLevel
+    {
+        private BundleImpl m_bundle;
+
+        private BundleStartLevelImpl(BundleImpl bundle)
+        {
+            m_bundle = bundle;
+        }
+
+        public Bundle getBundle()
+        {
+            return m_bundle;
+        }
+
+        public int getStartLevel()
+        {
+            return m_felix.getBundleStartLevel(m_bundle);
+        }
+
+        public void setStartLevel(int startlevel)
+        {
+            Object sm = System.getSecurityManager();
+
+            if (sm != null)
+            {
+                ((SecurityManager) sm).checkPermission(
+                    new AdminPermission(m_bundle, AdminPermission.EXECUTE));
+            }
+
+            if (m_bundle.getBundleId() == 0)
+            {
+                throw new IllegalArgumentException(
+                    "Cannot change system bundle start level.");
+            }
+            else if (startlevel <= 0)
+            {
+                throw new IllegalArgumentException(
+                    "Start level must be greater than zero.");
+            }
+            synchronized (m_requests)
+            {
+                // Start thread if necessary.
+                startThread();
+                // Synchronously persists the start level.
+                ((BundleImpl) m_bundle).setStartLevel(startlevel);
+                // Queue request.
+                m_requestListeners.add(null);
+                m_requests.add(new Object[] { m_bundle, new Integer(startlevel) });
+                m_requests.notifyAll();
+            }
+        }
+
+        public boolean isPersistentlyStarted()
+        {
+            return m_felix.isBundlePersistentlyStarted(m_bundle);
+        }
+
+        public boolean isActivationPolicyUsed()
+        {
+            return m_felix.isBundleActivationPolicyUsed(m_bundle);
+        }
+    }
+
+    public void run()
+    {
+        // This thread loops forever, thus it should
+        // be a daemon thread.
+        while (true)
+        {
+            Object request = null;
+            FrameworkListener[] listeners = null;
+            synchronized (m_requests)
+            {
+                // Wait for a request.
+                while (m_requests.isEmpty())
+                {
+                    // Terminate the thread if requested to do so (see stop()).
+                    if (m_thread == null)
+                    {
+                        return;
+                    }
+
+                    try
+                    {
+                        m_requests.wait();
+                    }
+                    catch (InterruptedException ex)
+                    {
+                        // Ignore.
+                    }
+                }
+
+                // Get the requested start level.
+                request = m_requests.remove(0);
+                listeners = m_requestListeners.remove(0);
+            }
+
+            // If the request object is an Integer, then the request
+            // is to set the framework start level. If the request is
+            // an Object array, then the request is to set the start
+            // level for a bundle.
+            // NOTE: We don't catch any exceptions here, because
+            // the invoked methods shield us from exceptions by
+            // catching Throwables when they invoke callbacks.
+            if (request instanceof Integer)
+            {
+                // Set the new framework start level.
+                m_felix.setActiveStartLevel(((Integer) request).intValue(), listeners);
+            }
+            else
+            {
+                Bundle bundle = (Bundle) ((Object[]) request)[BUNDLE_IDX];
+                int startlevel = ((Integer) ((Object[]) request)[STARTLEVEL_IDX]).intValue();
+                m_felix.setBundleStartLevel(bundle, startlevel);
+            }
+
+            // Notify any waiting thread that this request is done.
+            synchronized (request)
+            {
+                request.notifyAll();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/StartLevelActivator.java b/framework/src/main/java/org/apache/felix/framework/StartLevelActivator.java
index fcabcba..2888675 100644
--- a/framework/src/main/java/org/apache/felix/framework/StartLevelActivator.java
+++ b/framework/src/main/java/org/apache/felix/framework/StartLevelActivator.java
@@ -1,4 +1,4 @@
-/* 
+/*
  * 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
@@ -44,6 +44,5 @@
     public void stop(BundleContext context) throws Exception
     {
         m_reg.unregister();
-        m_startLevel.stop();
     }
 }
diff --git a/framework/src/main/java/org/apache/felix/framework/StartLevelImpl.java b/framework/src/main/java/org/apache/felix/framework/StartLevelImpl.java
index 2b3e112..74fed6f 100644
--- a/framework/src/main/java/org/apache/felix/framework/StartLevelImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/StartLevelImpl.java
@@ -1,4 +1,4 @@
-/* 
+/*
  * 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
@@ -18,69 +18,32 @@
  */
 package org.apache.felix.framework;
 
-import java.util.ArrayList;
-import java.util.List;
 
 import org.osgi.framework.AdminPermission;
 import org.osgi.framework.Bundle;
+import org.osgi.framework.startlevel.BundleStartLevel;
+import org.osgi.framework.startlevel.FrameworkStartLevel;
 import org.osgi.service.startlevel.StartLevel;
 
 /**
  * StartLevel service implementation.
  * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
 **/
-public class StartLevelImpl implements StartLevel, Runnable
+public class StartLevelImpl implements StartLevel
 {
-    static final String THREAD_NAME = "FelixStartLevel";
-
-    private static final int BUNDLE_IDX = 0;
-    private static final int STARTLEVEL_IDX = 1;
-
     private final Felix m_felix;
-    private final List m_requestList = new ArrayList();
-    private Thread m_thread = null;
 
     public StartLevelImpl(Felix felix)
     {
         m_felix = felix;
-        // Start a thread to perform asynchronous package refreshes.
-        m_thread = new Thread(this, "FelixStartLevel");
-        m_thread.setDaemon(true);
-        m_thread.start();
     }
-    
-    /**
-     * Stops the FelixStartLevel thread on system shutdown. Shutting down the
-     * thread explicitly is required in the embedded case, where Felix may be
-     * stopped without the Java VM being stopped. In this case the
-     * FelixStartLevel thread must be stopped explicitly.
-     * <p>
-     * This method is called by the
-     * {@link StartLevelActivator#stop(BundleContext)} method.
-     */
-    void stop()
-    {
-        synchronized (m_requestList)
-        {
-            if (m_thread != null)
-            {
-                // Null thread variable to signal to the thread that
-                // we want it to exit.
-                m_thread = null;
-                
-                // Wake up the thread, if it is currently in the wait() state
-                // for more work.
-                m_requestList.notifyAll();
-            }
-        }
-    }
-    
+
     /* (non-Javadoc)
      * @see org.osgi.service.startlevel.StartLevel#getStartLevel()
     **/
     public int getStartLevel()
     {
-        return m_felix.getActiveStartLevel();
+        return m_felix.adapt(FrameworkStartLevel.class).getStartLevel();
     }
 
     /* (non-Javadoc)
@@ -88,57 +51,7 @@
     **/
     public void setStartLevel(int startlevel)
     {
-        Object sm = System.getSecurityManager();
-        
-        if (sm != null)
-        {
-            ((SecurityManager) sm).checkPermission(
-                new AdminPermission(m_felix, AdminPermission.STARTLEVEL));
-        }
-        
-        if (startlevel <= 0)
-        {
-            throw new IllegalArgumentException(
-                "Start level must be greater than zero.");
-        }
-        
-        synchronized (m_requestList)
-        {
-            m_requestList.add(new Integer(startlevel));
-            m_requestList.notifyAll();
-        }
-    }
-
-    /**
-     * This method is currently only called by the by the thread that calls
-     * the Felix.start() method and the shutdown thread when the
-     * framework is shutting down.
-     * @param startlevel
-    **/
-    /* package */ void setStartLevelAndWait(int startlevel)
-    {
-        Object request = new Integer(startlevel);
-        synchronized (request)
-        {
-            synchronized (m_requestList)
-            {
-                m_requestList.add(request);
-                m_requestList.notifyAll();
-            }
-
-            try
-            {
-                request.wait();
-            }
-            catch (InterruptedException ex)
-            {
-                // Log it and ignore since it won't cause much of an issue.
-                m_felix.getLogger().log(
-                    Logger.LOG_WARNING,
-                    "Wait for start level change during shutdown interrupted.",
-                    ex);
-            }
-        }
+        m_felix.adapt(FrameworkStartLevel.class).setStartLevel(startlevel);
     }
 
     /* (non-Javadoc)
@@ -146,7 +59,7 @@
     **/
     public int getBundleStartLevel(Bundle bundle)
     {
-        return m_felix.getBundleStartLevel(bundle);
+        return bundle.adapt(BundleStartLevel.class).getStartLevel();
     }
 
     /* (non-Javadoc)
@@ -154,32 +67,7 @@
     **/
     public void setBundleStartLevel(Bundle bundle, int startlevel)
     {
-        Object sm = System.getSecurityManager();
-        
-        if (sm != null)
-        {
-            ((SecurityManager) sm).checkPermission(
-                new AdminPermission(bundle, AdminPermission.EXECUTE));
-        }
-        
-        if (bundle.getBundleId() == 0)
-        {
-            throw new IllegalArgumentException(
-                "Cannot change system bundle start level.");
-        }
-        else if (startlevel <= 0)
-        {
-            throw new IllegalArgumentException(
-                "Start level must be greater than zero.");
-        }
-        synchronized (m_requestList)
-        {
-            // Synchronously persists the start level.
-            ((BundleImpl) bundle).setStartLevel(startlevel);
-            // Asynchronously process the start level change.
-            m_requestList.add(new Object[] { bundle, new Integer(startlevel) });
-            m_requestList.notifyAll();
-        }
+        bundle.adapt(BundleStartLevel.class).setStartLevel(startlevel);
     }
 
     /* (non-Javadoc)
@@ -187,7 +75,7 @@
     **/
     public int getInitialBundleStartLevel()
     {
-        return m_felix.getInitialBundleStartLevel();
+        return m_felix.adapt(FrameworkStartLevel.class).getInitialBundleStartLevel();
     }
 
     /* (non-Javadoc)
@@ -195,14 +83,7 @@
     **/
     public void setInitialBundleStartLevel(int startlevel)
     {
-        Object sm = System.getSecurityManager();
-        
-        if (sm != null)
-        {
-            ((SecurityManager) sm).checkPermission(
-                new AdminPermission(m_felix, AdminPermission.STARTLEVEL));
-        }
-        m_felix.setInitialBundleStartLevel(startlevel);
+        m_felix.adapt(FrameworkStartLevel.class).setInitialBundleStartLevel(startlevel);
     }
 
     /* (non-Javadoc)
@@ -210,7 +91,7 @@
     **/
     public boolean isBundlePersistentlyStarted(Bundle bundle)
     {
-        return m_felix.isBundlePersistentlyStarted(bundle);
+        return bundle.adapt(BundleStartLevel.class).isPersistentlyStarted();
     }
 
     /* (non-Javadoc)
@@ -218,65 +99,6 @@
     **/
 	public boolean isBundleActivationPolicyUsed(Bundle bundle)
     {
-        return m_felix.isBundleActivationPolicyUsed(bundle);
+        return bundle.adapt(BundleStartLevel.class).isActivationPolicyUsed();
     }
-
-    public void run()
-    {
-        // This thread loops forever, thus it should
-        // be a daemon thread.
-        while (true)
-        {
-            Object request = null;
-            synchronized (m_requestList)
-            {
-                // Wait for a request.
-                while (m_requestList.size() == 0)
-                {
-                    // Terminate the thread if requested to do so (see stop()).
-                    if (m_thread == null)
-                    {
-                        return;
-                    }
-                    
-                    try
-                    {
-                        m_requestList.wait();
-                    }
-                    catch (InterruptedException ex)
-                    {
-                        // Ignore.
-                    }
-                }
-                
-                // Get the requested start level.
-                request = m_requestList.remove(0);
-            }
-
-            // If the request object is an Integer, then the request
-            // is to set the framework start level. If the request is
-            // an Object array, then the request is to set the start
-            // level for a bundle.
-            // NOTE: We don't catch any exceptions here, because
-            // the invoked methods shield us from exceptions by
-            // catching Throwables when they invoke callbacks.
-            if (request instanceof Integer)
-            {
-                // Set the new framework start level.
-                m_felix.setActiveStartLevel(((Integer) request).intValue());
-            }
-            else
-            {
-                Bundle bundle = (Bundle) ((Object[]) request)[BUNDLE_IDX];
-                int startlevel = ((Integer) ((Object[]) request)[STARTLEVEL_IDX]).intValue();
-                m_felix.setBundleStartLevel(bundle, startlevel);
-            }
-
-            // Notify any waiting thread that this request is done.
-            synchronized (request)
-            {
-                request.notifyAll();
-            }
-        }
-    }
-}
+}
\ No newline at end of file