You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2012/01/18 11:38:17 UTC

svn commit: r1232820 - in /sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl: BootstrapInstaller.java DirectoryUtil.java Sling.java SlingFelix.java StartupHandler.java

Author: cziegeler
Date: Wed Jan 18 10:38:16 2012
New Revision: 1232820

URL: http://svn.apache.org/viewvc?rev=1232820&view=rev
Log:
SLING-2372 : Detect startup mode

Added:
    sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/DirectoryUtil.java   (with props)
    sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/StartupHandler.java   (with props)
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/main/java/org/apache/sling/launchpad/base/impl/SlingFelix.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=1232820&r1=1232819&r2=1232820&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 Jan 18 10:38:16 2012
@@ -19,15 +19,12 @@
 package org.apache.sling.launchpad.base.impl;
 
 import java.io.File;
-import java.io.FileFilter;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.net.URL;
-import java.net.URLClassLoader;
 import java.util.Dictionary;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -96,11 +93,6 @@ class BootstrapInstaller {
     static final String PATH_BUNDLES = PATH_RESOURCES + "bundles";
 
     /**
-     * The possible file extensions for a bundle archive file.
-     */
-    private static final String[] BUNDLE_EXTENSIONS = { ".jar", ".war" };
-
-    /**
      * The header which contains the bundle's last modified date.
      */
     static final String BND_LAST_MODIFIED_HEADER = "Bnd-LastModified";
@@ -123,9 +115,6 @@ class BootstrapInstaller {
      */
     private 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 name of the bootstrap commands file
      */
@@ -175,7 +164,9 @@ class BootstrapInstaller {
         if (launchpadHome == null) {
             launchpadHome = bundleContext.getProperty(SharedConstants.SLING_HOME);
         }
-        File slingStartupDir = getSlingStartupDir(launchpadHome);
+        final File slingStartupDir = getSlingStartupDir(launchpadHome);
+
+        final StartupHandler startupHandler = new StartupHandler(this.bundleContext, this.logger, slingStartupDir);
 
         // execute bootstrap commands, if needed
         final BootstrapCommandFile cmd = new BootstrapCommandFile(logger,
@@ -185,11 +176,11 @@ class BootstrapInstaller {
         boolean shouldInstall = false;
 
         // see if the loading of bundles from the package is forced
-        String fpblString = bundleContext.getProperty(SharedConstants.FORCE_PACKAGE_BUNDLE_LOADING);
+        final String fpblString = bundleContext.getProperty(SharedConstants.FORCE_PACKAGE_BUNDLE_LOADING);
         if (Boolean.valueOf(fpblString)) {
             shouldInstall = true;
         } else {
-            shouldInstall = !isAlreadyInstalled(bundleContext, slingStartupDir);
+            shouldInstall = startupHandler.getMode() != StartupHandler.StartupMode.RESTART;
         }
 
         if (shouldInstall) {
@@ -240,13 +231,13 @@ class BootstrapInstaller {
             List<Bundle> installed = new LinkedList<Bundle>();
 
             // get all bundles from the startup location and install them
-            requireRestart |= installBundles(slingStartupDir, bundleContext, bySymbolicName, installed);
+            requireRestart |= installBundles(slingStartupDir, bySymbolicName, installed);
 
             // start all the newly installed bundles (existing bundles are not started if they are stopped)
             startBundles(installed);
 
             // mark everything installed
-            markInstalled(bundleContext, slingStartupDir);
+            startupHandler.finished();
         }
 
         // due to the upgrade of a framework extension bundle, the framework
@@ -316,7 +307,7 @@ class BootstrapInstaller {
             // path to the next resource
             String path = res.next();
 
-            if (isBundle(path)) {
+            if (DirectoryUtil.isBundle(path)) {
                 // try to access the bundle file, ignore if not possible
                 InputStream ins = resourceProvider.getResourceAsStream(path);
                 if (ins == null) {
@@ -353,21 +344,6 @@ class BootstrapInstaller {
     }
 
     /**
-     * Determine if a path could be a bundle based on its extension.
-     *
-     * @param path the path to the file
-     * @return true if the path could be a bundle
-     */
-    static boolean isBundle(String path) {
-        for (String extension : BUNDLE_EXTENSIONS) {
-            if (path.endsWith(extension)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
      * Copies a stream from the resource (jar/war) to a file
      * @param fromStream
      * @param toFile
@@ -409,19 +385,19 @@ class BootstrapInstaller {
      * @return <code>true</code> if a system bundle fragment was updated which
      *      requires the framework to restart.
      */
-    private boolean installBundles(File slingStartupDir,
-            BundleContext context, Map<String, Bundle> currentBundles,
-            List<Bundle> installed) {
+    private boolean installBundles(final File slingStartupDir,
+            final Map<String, Bundle> currentBundles,
+            final List<Bundle> installed) {
 
         // get the start level service (if possible) so we can set the initial start level
-        ServiceReference ref = context.getServiceReference(StartLevel.class.getName());
+        ServiceReference ref = bundleContext.getServiceReference(StartLevel.class.getName());
         StartLevel startLevelService = (ref != null)
-                ? (StartLevel) context.getService(ref)
+                ? (StartLevel) bundleContext.getService(ref)
                 : null;
 
         boolean requireRestart = false;
         try {
-            File[] directories = slingStartupDir.listFiles(DIRECTORY_FILTER);
+            File[] directories = slingStartupDir.listFiles(DirectoryUtil.DIRECTORY_FILTER);
             for (File levelDir : directories) {
                 // get startlevel from dir name
                 String dirName = levelDir.getName();
@@ -433,17 +409,17 @@ class BootstrapInstaller {
                 }
 
                 // iterate through all files in the startlevel dir
-                File[] bundleFiles = levelDir.listFiles(BUNDLE_FILE_FILTER);
+                File[] bundleFiles = levelDir.listFiles(DirectoryUtil.BUNDLE_FILE_FILTER);
                 for (File bundleFile : bundleFiles) {
                     requireRestart |= installBundle(bundleFile, startLevel,
-                        context, currentBundles, installed, startLevelService);
+                        currentBundles, installed, startLevelService);
                 }
             }
 
         } finally {
             // release the start level service
             if (ref != null) {
-                context.ungetService(ref);
+                bundleContext.ungetService(ref);
             }
         }
 
@@ -463,9 +439,11 @@ class BootstrapInstaller {
      * @return <code>true</code> if a system bundle fragment was updated which
      *      requires the framework to restart.
      */
-    private boolean installBundle(File bundleJar, int startLevel,
-            BundleContext context, Map<String, Bundle> currentBundles,
-            List<Bundle> installed, StartLevel startLevelService) {
+    private boolean installBundle(final File bundleJar,
+            final int startLevel,
+            final Map<String, Bundle> currentBundles,
+            final List<Bundle> installed,
+            final StartLevel startLevelService) {
         // get the manifest for the bundle information
         Manifest manifest = getManifest(bundleJar);
         if (manifest == null) {
@@ -528,7 +506,7 @@ class BootstrapInstaller {
             String location = SCHEME
                 + path.substring(path.lastIndexOf('/') + 1);
             try {
-                Bundle theBundle = context.installBundle(location, ins);
+                Bundle theBundle = bundleContext.installBundle(location, ins);
                 logger.log(Logger.LOG_INFO, "Bundle "
                     + theBundle.getSymbolicName() + " installed from "
                     + location);
@@ -556,15 +534,15 @@ class BootstrapInstaller {
      * provides an active <code>StartLevel</code> service, the start levels of
      * the Bundles is first set to <em>1</em>.
      */
-    private void startBundles(List<Bundle> bundles) {
+    private void startBundles(final List<Bundle> bundles) {
 
         // start all bundles
-        for (Bundle bundle : bundles) {
+        for (final Bundle bundle : bundles) {
             try {
                 if (!isFragment(bundle)) {
                     bundle.start();
                 }
-            } catch (BundleException be) {
+            } catch (final BundleException be) {
                 logger.log(Logger.LOG_ERROR, "Bundle "
                     + bundle.getSymbolicName() + " could not be started", be);
             }
@@ -572,8 +550,8 @@ class BootstrapInstaller {
 
     }
 
-    private int getStartLevel(String path) {
-        String name = path.substring(path.lastIndexOf('/') + 1);
+    private int getStartLevel(final String path) {
+        final String name = path.substring(path.lastIndexOf('/') + 1);
         try {
             int level = Integer.parseInt(name);
             if (level >= 0) {
@@ -582,7 +560,7 @@ class BootstrapInstaller {
 
             logger.log(Logger.LOG_ERROR, "Illegal Runlevel for " + path
                 + ", ignoring");
-        } catch (NumberFormatException nfe) {
+        } catch (final NumberFormatException nfe) {
             logger.log(Logger.LOG_INFO, "Folder " + path
                 + " does not denote start level, ignoring");
         }
@@ -608,7 +586,7 @@ class BootstrapInstaller {
      * @param jarPath The path to the JAR file provided by the resource provider
      *            of this instance.
      */
-    private Manifest getManifest(File jar) {
+    private Manifest getManifest(final File jar) {
         JarFile jarFile = null;
         try {
             jarFile = new JarFile(jar, false);
@@ -637,7 +615,7 @@ class BootstrapInstaller {
      *
      * @param manifest The Manifest from which to extract the header.
      */
-    static String getBundleSymbolicName(Manifest manifest) {
+    static String getBundleSymbolicName(final Manifest manifest) {
         return manifest.getMainAttributes().getValue(
             Constants.BUNDLE_SYMBOLICNAME);
     }
@@ -652,7 +630,7 @@ class BootstrapInstaller {
      * @return <code>true</code> if the manifest does not describe a bundle with
      *         a higher version number.
      */
-    private boolean ignore(Bundle installedBundle, Manifest manifest) {
+    private boolean ignore(final Bundle installedBundle, final Manifest manifest) {
 
         // the bundle is not installed yet, so we have to install it
         if (installedBundle == null) {
@@ -684,7 +662,7 @@ class BootstrapInstaller {
      * Returns <code>true</code> if the bundle must be assumed to be a fragment
      * according to its <code>Fragment-Host</code> header.
      */
-    private static boolean isFragment(Bundle bundle) {
+    private static boolean isFragment(final Bundle bundle) {
         Dictionary<?, ?> headerMap = bundle.getHeaders();
         return headerMap.get(Constants.FRAGMENT_HOST) != null;
     }
@@ -698,7 +676,7 @@ class BootstrapInstaller {
      * @return true if the to-be-installed bundle is newer or if the comparison
      *         fails for some reason
      */
-    private boolean isNewerSnapshot(Bundle installedBundle, Manifest manifest) {
+    private boolean isNewerSnapshot(final Bundle installedBundle, final Manifest manifest) {
         String installedDate = (String) installedBundle.getHeaders().get(
             BND_LAST_MODIFIED_HEADER);
         String toBeInstalledDate = manifest.getMainAttributes().getValue(
@@ -738,143 +716,6 @@ class BootstrapInstaller {
 
     }
 
-    // ---------- Bundle Installation marker file
-
-    private boolean isAlreadyInstalled(BundleContext context,
-            File slingStartupDir) {
-        final File dataFile = context.getDataFile(DATA_FILE);
-        if (dataFile != null && dataFile.exists()) {
-
-            FileInputStream fis = null;
-            try {
-
-                long selfStamp = getSelfTimestamp(slingStartupDir);
-                if (selfStamp > 0) {
-
-                    fis = new FileInputStream(dataFile);
-                    byte[] bytes = new byte[20];
-                    int len = fis.read(bytes);
-                    String value = new String(bytes, 0, len);
-
-                    long storedStamp = Long.parseLong(value);
-
-                    logger.log(Logger.LOG_INFO, String.format("Stored timestamp: %s", storedStamp));
-
-                    return storedStamp >= selfStamp;
-                }
-
-            } catch (NumberFormatException nfe) {
-                // probably still the old value, fallback to assume not
-                // installed
-
-            } catch (IOException ioe) {
-                logger.log(Logger.LOG_ERROR,
-                    "IOException during reading of installed flag.", ioe);
-
-            } finally {
-                if (fis != null) {
-                    try {
-                        fis.close();
-                    } catch (IOException ignore) {
-                    }
-                }
-            }
-        }
-
-        // fallback assuming not installed yet
-        return false;
-    }
-
-    private void markInstalled(BundleContext context, File slingStartupDir) {
-        final File dataFile = context.getDataFile(DATA_FILE);
-        try {
-            final FileOutputStream fos = new FileOutputStream(dataFile);
-            try {
-                fos.write(String.valueOf(getSelfTimestamp(slingStartupDir)).getBytes());
-            } finally {
-                try {
-                    fos.close();
-                } catch (IOException ignore) {
-                }
-            }
-        } catch (IOException ioe) {
-            logger.log(Logger.LOG_ERROR,
-                "IOException during writing of installed flag.", ioe);
-        }
-    }
-
-    /**
-     * Returns the time stamp of JAR file from which this class has been loaded
-     * or -1 if the timestamp cannot be resolved.
-     * <p>
-     * This method assumes that the ClassLoader of this class is an
-     * URLClassLoader and that the first URL entry of this class loader is the
-     * JAR providing this class. This is in fact true as the URLClassLoader has
-     * been created by the launcher from the launcher JAR file.
-     *
-     * @return The last modification time stamp of the launcher JAR file or -1
-     *         if the class loader of this class is not an URLClassLoader or the
-     *         class loader has no URL entries. Both situations are not really
-     *         expected.
-     * @throws IOException If an error occurrs reading accessing the last
-     *             modification time stampe.
-     */
-    private long getSelfTimestamp(File slingStartupDir) throws IOException {
-
-        // the timestamp of the launcher jar
-        long selfStamp = -1;
-        ClassLoader loader = getClass().getClassLoader();
-        if (loader instanceof URLClassLoader) {
-            URLClassLoader urlLoader = (URLClassLoader) loader;
-            URL[] urls = urlLoader.getURLs();
-            if (urls.length > 0) {
-            	URL url = urls[0];
-            	logger.log(Logger.LOG_INFO, String.format("Using timestamp from %s.", url));
-                selfStamp = urls[0].openConnection().getLastModified();
-            }
-        }
-
-        // check whether any bundle is younger than the launcher jar
-        File[] directories = slingStartupDir.listFiles(DIRECTORY_FILTER);
-        for (File levelDir : directories) {
-
-            // iterate through all files in the startlevel dir
-            File[] jarFiles = levelDir.listFiles(BUNDLE_FILE_FILTER);
-            for (File bundleJar : jarFiles) {
-                if (bundleJar.lastModified() > selfStamp) {
-                	logger.log(Logger.LOG_INFO, String.format("Using timestamp from %s.", bundleJar));
-                    selfStamp = bundleJar.lastModified();
-                }
-            }
-        }
-
-        logger.log(Logger.LOG_INFO, String.format("Final self timestamp: %s.", selfStamp));
-
-        // return the final stamp (may be -1 if launcher jar cannot be checked
-        // and there are no bundle jar files)
-        return selfStamp;
-    }
-
-    //---------- FileFilter implementations to scan startup folders
-
-    /**
-     * Simple directory filter
-     */
-    private static final FileFilter DIRECTORY_FILTER = new FileFilter() {
-        public boolean accept(File f) {
-            return f.isDirectory();
-        }
-    };
-
-    /**
-     * Simple bundle file filter
-     */
-    private static final FileFilter BUNDLE_FILE_FILTER = new FileFilter() {
-        public boolean accept(File f) {
-            return f.isFile() && isBundle(f.getName());
-        }
-    };
-
     //---------- helper
     /**
      * Simple check to see if a string is blank since
@@ -882,7 +723,7 @@ class BootstrapInstaller {
      * @param str the string to check
      * @return true if the string is null or empty OR false otherwise
      */
-    static boolean isBlank(String str) {
+    static boolean isBlank(final String str) {
         return str == null || str.length() == 0 || str.trim().length() == 0;
     }
 

Added: sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/DirectoryUtil.java
URL: http://svn.apache.org/viewvc/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/DirectoryUtil.java?rev=1232820&view=auto
==============================================================================
--- sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/DirectoryUtil.java (added)
+++ sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/DirectoryUtil.java Wed Jan 18 10:38:16 2012
@@ -0,0 +1,63 @@
+/*
+ * 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.sling.launchpad.base.impl;
+
+import java.io.File;
+import java.io.FileFilter;
+
+public class DirectoryUtil {
+
+    /**
+     * The possible file extensions for a bundle archive file.
+     */
+    private static final String[] BUNDLE_EXTENSIONS = { ".jar", ".war" };
+
+    //---------- FileFilter implementations to scan startup folders
+
+    /**
+     * Simple directory filter
+     */
+    public static final FileFilter DIRECTORY_FILTER = new FileFilter() {
+        public boolean accept(File f) {
+            return f.isDirectory();
+        }
+    };
+
+    /**
+     * Simple bundle file filter
+     */
+    public static final FileFilter BUNDLE_FILE_FILTER = new FileFilter() {
+        public boolean accept(File f) {
+            return f.isFile() && isBundle(f.getName());
+        }
+    };
+
+    /**
+     * Determine if a path could be a bundle based on its extension.
+     *
+     * @param path the path to the file
+     * @return true if the path could be a bundle
+     */
+    public static boolean isBundle(String path) {
+        for (String extension : BUNDLE_EXTENSIONS) {
+            if (path.endsWith(extension)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}

Propchange: sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/DirectoryUtil.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/DirectoryUtil.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/DirectoryUtil.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

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=1232820&r1=1232819&r2=1232820&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 Jan 18 10:38:16 2012
@@ -195,9 +195,11 @@ public class Sling {
      *
      * @throws BundleException if the framework cannot be initialized.
      */
-    public Sling(Notifiable notifiable, Logger logger,
-            LaunchpadContentProvider resourceProvider, Map<String, String> propOverwrite)
-            throws BundleException {
+    public Sling(final Notifiable notifiable,
+            final Logger logger,
+            final LaunchpadContentProvider resourceProvider,
+            final Map<String, String> propOverwrite)
+    throws BundleException {
 
         this.logger = logger;
         this.resourceProvider = resourceProvider;

Modified: sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/SlingFelix.java
URL: http://svn.apache.org/viewvc/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/SlingFelix.java?rev=1232820&r1=1232819&r2=1232820&view=diff
==============================================================================
--- sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/SlingFelix.java (original)
+++ sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/SlingFelix.java Wed Jan 18 10:38:16 2012
@@ -35,7 +35,7 @@ public class SlingFelix extends Felix {
 
     private Thread notifierThread;
 
-    public SlingFelix(Notifiable notifiable, Map<?, ?> props) throws Exception {
+    public SlingFelix(final Notifiable notifiable, final Map<?, ?> props) throws Exception {
         super(props);
         this.notifiable = notifiable;
     }
@@ -46,7 +46,7 @@ public class SlingFelix extends Felix {
     }
 
     @Override
-    public void update(InputStream is) throws BundleException {
+    public void update(final InputStream is) throws BundleException {
         // get the update file and make sure, the stream is closed
         try {
             startNotifier(true, is);
@@ -69,12 +69,12 @@ public class SlingFelix extends Felix {
         super.stop();
     }
 
-    public void stop(int status) throws BundleException {
+    public void stop(final int status) throws BundleException {
         startNotifier(false, null);
         super.stop(status);
     }
 
-    private synchronized void startNotifier(boolean restart, InputStream ins) {
+    private synchronized void startNotifier(final boolean restart, final InputStream ins) {
         if (notifierThread == null) {
             notifierThread = new Thread(new Notifier(restart, ins),
                 "Sling Notifier");
@@ -89,7 +89,7 @@ public class SlingFelix extends Felix {
 
         private final File updateFile;
 
-        private Notifier(boolean restart, InputStream ins) {
+        private Notifier(final boolean restart, final InputStream ins) {
             this.restart = restart;
 
             if (ins != null) {

Added: sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/StartupHandler.java
URL: http://svn.apache.org/viewvc/sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/StartupHandler.java?rev=1232820&view=auto
==============================================================================
--- sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/StartupHandler.java (added)
+++ sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/StartupHandler.java Wed Jan 18 10:38:16 2012
@@ -0,0 +1,199 @@
+/*
+ * 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.sling.launchpad.base.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import org.apache.felix.framework.Logger;
+import org.apache.sling.launchpad.api.LaunchpadContentProvider;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The <code>StartupHandler</code> tries to detect the startup mode:
+ * It distinguishes between an initial startup (INSTALL), an update (UPDATE)
+ * and a restart without a change (RESTART).
+ * @since 2.4.0
+ */
+class StartupHandler {
+
+    public enum StartupMode {
+        INSTALL,
+        UPDATE,
+        RESTART
+    }
+
+    /** 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.
+     */
+    private final Logger logger;
+
+    /** The bundle context. */
+    private final BundleContext bundleContext;
+
+    private final StartupMode mode;
+
+    private final File startupDir;
+
+    private final long selfStamp;
+
+    StartupHandler(final BundleContext bundleContext,
+            final Logger logger,
+            final File slingStartupDir)
+    throws IOException {
+        this.logger = logger;
+        this.bundleContext = bundleContext;
+        this.startupDir = slingStartupDir;
+        this.selfStamp = this.getSelfTimestamp();
+        this.mode = detectMode();
+        this.logger.log(Logger.LOG_INFO, "Starting in mode " + this.mode);
+    }
+
+    public StartupMode getMode() {
+        return this.mode;
+    }
+
+    public void finished() {
+        this.markInstalled();
+    }
+
+    private StartupMode detectMode() {
+        final File dataFile = this.bundleContext.getDataFile(DATA_FILE);
+        if (dataFile != null && dataFile.exists()) {
+
+            FileInputStream fis = null;
+            try {
+                if (this.selfStamp > 0) {
+
+                    fis = new FileInputStream(dataFile);
+                    byte[] bytes = new byte[20];
+                    int len = fis.read(bytes);
+                    String value = new String(bytes, 0, len);
+
+                    long storedStamp = Long.parseLong(value);
+
+                    logger.log(Logger.LOG_INFO, String.format("Stored timestamp: %s", storedStamp));
+
+                    return (storedStamp >= this.selfStamp ? StartupMode.RESTART : StartupMode.UPDATE);
+                }
+
+            } catch (final NumberFormatException nfe) {
+                // probably still the old value, fallback to assume not
+                // installed
+                return StartupMode.RESTART;
+
+            } catch (final IOException ioe) {
+                logger.log(Logger.LOG_ERROR,
+                    "IOException during reading of installed flag.", ioe);
+
+            } finally {
+                if (fis != null) {
+                    try { fis.close(); } catch (IOException ignore) {}
+                }
+            }
+        }
+        // not installed yet - fallback
+        return StartupMode.INSTALL;
+    }
+
+    private long getTimeStampOfClass(final Class<?> clazz)
+    throws IOException {
+        long selfStamp = -1;
+        final ClassLoader loader = clazz.getClassLoader();
+        if (loader instanceof URLClassLoader) {
+            final URLClassLoader urlLoader = (URLClassLoader) loader;
+            final URL[] urls = urlLoader.getURLs();
+            if (urls.length > 0) {
+                final URL url = urls[0];
+                logger.log(Logger.LOG_INFO, String.format("Using timestamp from %s.", url));
+                selfStamp = urls[0].openConnection().getLastModified();
+            }
+        }
+        return selfStamp;
+    }
+
+    /**
+     * Returns the time stamp of JAR file from which this class has been loaded
+     * or -1 if the timestamp cannot be resolved.
+     * <p>
+     * This method assumes that the ClassLoader of this class is an
+     * URLClassLoader and that the first URL entry of this class loader is the
+     * JAR providing this class. This is in fact true as the URLClassLoader has
+     * been created by the launcher from the launcher JAR file.
+     *
+     * @return The last modification time stamp of the launcher JAR file or -1
+     *         if the class loader of this class is not an URLClassLoader or the
+     *         class loader has no URL entries. Both situations are not really
+     *         expected.
+     * @throws IOException If an error occurrs reading accessing the last
+     *             modification time stampe.
+     */
+    private long getSelfTimestamp() throws IOException {
+
+        // the timestamp of the launcher jar and the bootstrap jar
+        long selfStamp = this.getTimeStampOfClass(this.getClass());
+        long bootStamp = this.getTimeStampOfClass(LaunchpadContentProvider.class);
+        if ( bootStamp > selfStamp ) {
+            selfStamp = bootStamp;
+        }
+
+        // check whether any bundle is younger than the launcher jar
+        final File[] directories = this.startupDir.listFiles(DirectoryUtil.DIRECTORY_FILTER);
+        for (final File levelDir : directories) {
+
+            // iterate through all files in the startlevel dir
+            final File[] jarFiles = levelDir.listFiles(DirectoryUtil.BUNDLE_FILE_FILTER);
+            for (final File bundleJar : jarFiles) {
+                if (bundleJar.lastModified() > selfStamp) {
+                    logger.log(Logger.LOG_INFO, String.format("Using timestamp from %s.", bundleJar));
+                    selfStamp = bundleJar.lastModified();
+                }
+            }
+        }
+
+        logger.log(Logger.LOG_INFO, String.format("Final self timestamp: %s.", selfStamp));
+
+        // return the final stamp (may be -1 if launcher jar cannot be checked
+        // and there are no bundle jar files)
+        return selfStamp;
+    }
+
+    private void markInstalled() {
+        final File dataFile = this.bundleContext.getDataFile(DATA_FILE);
+        try {
+            final FileOutputStream fos = new FileOutputStream(dataFile);
+            try {
+                fos.write(String.valueOf(this.selfStamp).getBytes());
+            } finally {
+                try { fos.close(); } catch (final IOException ignore) {}
+            }
+        } catch (final IOException ioe) {
+            logger.log(Logger.LOG_ERROR,
+                "IOException during writing of installed flag.", ioe);
+        }
+    }
+}

Propchange: sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/StartupHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/StartupHandler.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/launchpad/base/src/main/java/org/apache/sling/launchpad/base/impl/StartupHandler.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain