Author: fmeschbe
Date: Wed Nov 25 10:40:57 2009
New Revision: 884022

URL: http://svn.apache.org/viewvc?rev=884022&view=rev
Log:
SLING-1200 The BootstrapInstaller class takes over control of whether to go 
into the configured start level after initial installation or restart the 
framework. To this avail the framework startlevel property is set to one to not 
start all bundles in the first place should the framework be restarted.

Modified:
    
sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/BootstrapInstaller.java
    
sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/Sling.java
    
sling/trunk/launchpad/base/src/test/java/org/apache/sling/launchpad/base/impl/BootstrapInstallerTest.java

Modified: 
sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/BootstrapInstaller.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/BootstrapInstaller.java?rev=884022&r1=884021&r2=884022&view=diff
==============================================================================
--- 
sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/BootstrapInstaller.java
 (original)
+++ 
sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/BootstrapInstaller.java
 Wed Nov 25 10:40:57 2009
@@ -43,6 +43,8 @@
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.Version;
 import org.osgi.service.startlevel.StartLevel;
@@ -56,7 +58,7 @@
  * to 1 and started. Any bundle already installed is not installed again and
  * will also not be started here.
  */
-class BootstrapInstaller implements BundleActivator {
+class BootstrapInstaller implements BundleActivator, FrameworkListener {
 
     /**
      * The Bundle location scheme (protocol) used for bundles installed by this
@@ -111,6 +113,9 @@
      */
     static final int STARTLEVEL_NONE = -1;
 
+    /** The data file which works as a marker to detect the first startup. */
+    private static final String DATA_FILE = "bootstrapinstaller.ser";
+
     /**
      * The {...@link Logger} use for logging messages during installation and
      * startup.
@@ -123,12 +128,29 @@
      */
     private final ResourceProvider resourceProvider;
 
-    /** The data file which works as a marker to detect the first startup. */
-    private static final String DATA_FILE = "bootstrapinstaller.ser";
+    private BundleContext bundleContext;
+
+    /**
+     * The OSGi start level into which the framework is taken by the
+     * {...@link #frameworkEvent(FrameworkEvent)} method when the framework has
+     * reached the originally specified start level.
+     * <p>
+     * If this value is smaller than 1 the framework is restarted. This is
+     * particularly the case if the {...@link #start(BundleContext)} method 
causes
+     * the update of an installed framework extension bundle.
+     * <p>
+     * This value is preset by the
+     * {...@link #BootstrapInstaller(Logger, ResourceProvider, Map)} 
constructor to
+     * the value set in the 
<code>org.osgi.framework.startlevel.beginning</code>
+     * property of the supplied map.
+     */
+    private int targetStartLevel;
 
-    BootstrapInstaller(Logger logger, ResourceProvider resourceProvider) {
+    BootstrapInstaller(Logger logger, ResourceProvider resourceProvider,
+            Map<String, String> props) {
         this.logger = logger;
         this.resourceProvider = resourceProvider;
+        this.targetStartLevel = getStartLevel(props);
     }
 
     //---------- BundleActivator interface
@@ -150,7 +172,13 @@
      *   So you could place your bundles in that structure and get them 
installed
      *   at the requested start level (0 being "default bundle start level").
      */
-    public void start(BundleContext context) throws Exception {
+    public void start(final BundleContext context) throws Exception {
+
+        // prepare for further startup after initial startup
+        // see the frameworkEvent method for details
+        this.bundleContext = context;
+        this.bundleContext.addFrameworkListener(this);
+
         // get the startup location in sling home
         String slingHome = context.getProperty(SharedConstants.SLING_HOME);
         File slingStartupDir = getSlingStartupDir(slingHome);
@@ -217,18 +245,89 @@
             List<Bundle> installed = new LinkedList<Bundle>();
 
             // get all bundles from the startup location and install them
-            installBundles(slingStartupDir, context, bySymbolicName, 
installed);
+            boolean requireRestart = installBundles(slingStartupDir, context, 
bySymbolicName, installed);
 
             // start all the newly installed bundles (existing bundles are not 
started if they are stopped)
             startBundles(installed);
 
             // mark everything installed
             markInstalled(context, slingStartupDir);
+
+            // due to the upgrade of a framework extension bundle, the 
framework
+            // has to be restarted. For this reason, set the target start level
+            // to a negative value.
+            if (requireRestart) {
+                logger.log(
+                    Logger.LOG_INFO,
+                    "Framework extension(s) have been updated, restarting 
framework after startup has completed");
+
+                targetStartLevel = -1;
+            }
         }
     }
 
     /** Nothing to be done on stop */
     public void stop(BundleContext context) {
+        this.bundleContext = null;
+    }
+
+    //---------- Framework Listener
+
+    /**
+     * Called whenever a framework event is taking place. This method only 
cares
+     * for the framework event emitted once the framework startup has 
completed.
+     * Once the framework startup has completed, this method takes further
+     * actions (besides unregistering as a framework listener):
+     * <ul>
+     * <li>If bundle installation in the {...@link #start(BundleContext)} 
method
+     * included an update of a framework extension fragment bundle, the
+     * framework has to be restarted. This is effectuated by calling the
+     * <code>Bundle.update()</code> method on the system bundle.</li>
+     * <li>If a restart is not required, the StartLevel service is instructed 
to
+     * raise the framework start level to the value requested by the framework
+     * launcher.</li>
+     * </ul>
+     */
+    public void frameworkEvent(FrameworkEvent event) {
+        if (event.getType() == FrameworkEvent.STARTED) {
+
+            // don't care for further events
+            this.bundleContext.removeFrameworkListener(this);
+
+            if (targetStartLevel < 1) {
+
+                // restart
+                logger.log(Logger.LOG_INFO,
+                    "Restarting framework to resolve new framework 
extension(s)");
+                try {
+                    bundleContext.getBundle(0).update();
+                } catch (BundleException be) {
+                    logger.log(
+                        Logger.LOG_ERROR,
+                        "Failed restarting to resolve new framework 
extension(s)",
+                        be);
+                }
+
+            } else {
+
+                // raise start level to the desired target
+                ServiceReference sr = 
bundleContext.getServiceReference(StartLevel.class.getName());
+                if (sr != null) {
+                    StartLevel sl = (StartLevel) bundleContext.getService(sr);
+                    try {
+                        logger.log(Logger.LOG_INFO, "Setting start level to "
+                            + targetStartLevel);
+                        sl.setStartLevel(targetStartLevel);
+                    } finally {
+                        bundleContext.ungetService(sr);
+                    }
+                } else {
+                    logger.log(Logger.LOG_WARNING,
+                        "StartLevel service not available, will not set the 
start level");
+                }
+
+            }
+        }
     }
 
     //---------- Startup folder maintenance
@@ -363,8 +462,11 @@
      *            considered for installation.
      * @param installed The list of Bundles installed by this method. Each
      *            Bundle successfully installed is added to this list.
+     *
+     * @return <code>true</code> if a system bundle fragment was updated which
+     *      requires the framework to restart.
      */
-    private void installBundles(File slingStartupDir,
+    private boolean installBundles(File slingStartupDir,
             BundleContext context, Map<String, Bundle> currentBundles,
             List<Bundle> installed) {
 
@@ -374,6 +476,7 @@
                 ? (StartLevel) context.getService(ref)
                 : null;
 
+        boolean requireRestart = false;
         try {
             File[] directories = slingStartupDir.listFiles(DIRECTORY_FILTER);
             for (File levelDir : directories) {
@@ -389,7 +492,8 @@
                 // iterate through all files in the startlevel dir
                 File[] jarFiles = levelDir.listFiles(JAR_FILE_FILTER);
                 for (File bundleJar : jarFiles) {
-                    installBundle(bundleJar, startLevel, context, 
currentBundles, installed, startLevelService);
+                    requireRestart |= installBundle(bundleJar, startLevel,
+                        context, currentBundles, installed, startLevelService);
                 }
             }
 
@@ -399,6 +503,8 @@
                 context.ungetService(ref);
             }
         }
+
+        return requireRestart;
     }
 
     /**
@@ -410,8 +516,11 @@
      * @param installed The list of Bundles installed by this method. Each
      *            Bundle successfully installed is added to this list.
      * @param startLevelService the service which sets the start level
+     *
+     * @return <code>true</code> if a system bundle fragment was updated which
+     *      requires the framework to restart.
      */
-    private void installBundle(File bundleJar, int startLevel,
+    private boolean installBundle(File bundleJar, int startLevel,
             BundleContext context, Map<String, Bundle> currentBundles,
             List<Bundle> installed, StartLevel startLevelService) {
         // get the manifest for the bundle information
@@ -419,7 +528,7 @@
         if (manifest == null) {
             logger.log(Logger.LOG_ERROR, "Ignoring " + bundleJar
                 + ": Cannot read manifest");
-            return; // SHORT CIRCUIT
+            return false; // SHORT CIRCUIT
         }
 
         // ensure a symbolic name in the jar file
@@ -428,7 +537,7 @@
             logger.log(Logger.LOG_ERROR, "Ignoring " + bundleJar
                 + ": Missing " + Constants.BUNDLE_SYMBOLICNAME
                 + " in manifest");
-            return; // SHORT CIRCUIT
+            return false; // SHORT CIRCUIT
         }
 
         // check for an installed Bundle with the symbolic name
@@ -436,7 +545,7 @@
         if (ignore(installedBundle, manifest)) {
             logger.log(Logger.LOG_INFO, "Ignoring " + bundleJar
                 + ": More recent version already installed");
-            return; // SHORT CIRCUIT
+            return false; // SHORT CIRCUIT
         }
 
         // try to access the JAR file, ignore if not possible
@@ -444,10 +553,17 @@
         try {
             ins = new FileInputStream(bundleJar);
         } catch (FileNotFoundException e) {
-            return; // SHORT CIRCUIT
+            return false; // SHORT CIRCUIT
         }
 
+        final boolean requireRestart;
         if (installedBundle != null) {
+
+            // if the installed bundle is an extension fragment we have to
+            // restart the framework after completing the installation
+            // or upgrade of all new bundles
+            requireRestart = isSystemBundleFragment(installedBundle);
+
             try {
                 installedBundle.update(ins);
                 logger.log(Logger.LOG_INFO, "Bundle "
@@ -459,6 +575,11 @@
             }
 
         } else {
+
+            // restart is not required for any bundle installation at this
+            // stage
+            requireRestart = false;
+
             // install the JAR file as a bundle
             String path = bundleJar.getPath();
             String location = SCHEME
@@ -483,6 +604,8 @@
                     "Bundle installation from " + location + " failed", be);
             }
         }
+
+        return requireRestart;
     }
 
     /**
@@ -523,6 +646,13 @@
         return STARTLEVEL_NONE;
     }
 
+    private boolean isSystemBundleFragment(final Bundle installedBundle) {
+        final String fragmentHeader = (String) 
installedBundle.getHeaders().get(
+            Constants.FRAGMENT_HOST);
+        return fragmentHeader != null
+            && fragmentHeader.indexOf(Constants.EXTENSION_DIRECTIVE) > 0;
+    }
+
     // ---------- Bundle JAR file information
 
     /**
@@ -751,6 +881,24 @@
 
     //---------- helper
 
+    private static int getStartLevel(Map<String, String> props) {
+        // check requested startlevel from the startup properties
+        final String startLevelS = 
props.get(Constants.FRAMEWORK_BEGINNING_STARTLEVEL);
+        if (startLevelS != null) {
+            try {
+                int startLevel = Integer.parseInt(startLevelS);
+                if (startLevel >= 1) {
+                    return startLevel;
+                }
+            } catch (NumberFormatException nfe) {
+                // don't care much
+            }
+        }
+
+        // fall back to default startlevel
+        return 1;
+    }
+
     /**
      * Simple check to see if a string is blank since
      * StringUtils is not available here, maybe fix this later

Modified: 
sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/Sling.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/Sling.java?rev=884022&r1=884021&r2=884022&view=diff
==============================================================================
--- 
sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/Sling.java
 (original)
+++ 
sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/Sling.java
 Wed Nov 25 10:40:57 2009
@@ -221,10 +221,16 @@
         // ensure execution environment
         this.setExecutionEnvironment(props);
 
+        // prepare bootstrap installer and ensure the framework only goes into
+        // level 1 in the first place
+        final BootstrapInstaller bi = new BootstrapInstaller(logger,
+            resourceProvider, props);
+        props.put(Constants.FRAMEWORK_BEGINNING_STARTLEVEL, "1");
+
         // the custom activator list just contains this servlet
         List<BundleActivator> activators = new ArrayList<BundleActivator>();
         activators.add(this);
-        activators.add(new BootstrapInstaller(logger, resourceProvider));
+        activators.add(bi);
 
         // create the framework and start it
         Map<String, Object> felixProps = new HashMap<String, Object>(props);

Modified: 
sling/trunk/launchpad/base/src/test/java/org/apache/sling/launchpad/base/impl/BootstrapInstallerTest.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/launchpad/base/src/test/java/org/apache/sling/launchpad/base/impl/BootstrapInstallerTest.java?rev=884022&r1=884021&r2=884022&view=diff
==============================================================================
--- 
sling/trunk/launchpad/base/src/test/java/org/apache/sling/launchpad/base/impl/BootstrapInstallerTest.java
 (original)
+++ 
sling/trunk/launchpad/base/src/test/java/org/apache/sling/launchpad/base/impl/BootstrapInstallerTest.java
 Wed Nov 25 10:40:57 2009
@@ -25,6 +25,7 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.HashMap;
 import java.util.jar.Manifest;
 
 import org.junit.Test;
@@ -192,7 +193,7 @@
      */
     @Test
     public void testGetManifestInputStream() {
-        BootstrapInstaller bsi = new BootstrapInstaller(null, null);
+        BootstrapInstaller bsi = new BootstrapInstaller(null, null, new 
HashMap<String, String>());
         InputStream is = 
Thread.currentThread().getContextClassLoader().getResourceAsStream(
             "holaworld.jar");
         Manifest m = bsi.getManifest(is);
@@ -211,7 +212,7 @@
      */
     @Test
     public void testGetBundleSymbolicName() {
-        BootstrapInstaller bsi = new BootstrapInstaller(null, null);
+        BootstrapInstaller bsi = new BootstrapInstaller(null, null, new 
HashMap<String, String>());
         InputStream is = 
Thread.currentThread().getContextClassLoader().getResourceAsStream(
             "holaworld.jar");
         Manifest m = bsi.getManifest(is);


Reply via email to