Save This Page
Home » org.apache.sling.launchpad.base-2.2.0-source-release » org.apache.sling.launchpad.base.shared » [javadoc | source]
    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements.  See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership.  The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License.  You may obtain a copy of the License at
    9    *
   10    *   http://www.apache.org/licenses/LICENSE-2.0
   11    *
   12    * Unless required by applicable law or agreed to in writing,
   13    * software distributed under the License is distributed on an
   14    * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   15    * KIND, either express or implied.  See the License for the
   16    * specific language governing permissions and limitations
   17    * under the License.
   18    */
   19   package org.apache.sling.launchpad.base.shared;
   20   
   21   import java.beans.Introspector;
   22   import java.io.File;
   23   import java.io.FileFilter;
   24   import java.io.FileOutputStream;
   25   import java.io.IOException;
   26   import java.io.InputStream;
   27   import java.io.OutputStream;
   28   import java.net.JarURLConnection;
   29   import java.net.MalformedURLException;
   30   import java.net.URI;
   31   import java.net.URL;
   32   import java.net.URLConnection;
   33   import java.util.ArrayList;
   34   import java.util.Collections;
   35   import java.util.Date;
   36   import java.util.List;
   37   import java.util.jar.JarFile;
   38   
   39   import org.apache.sling.commons.osgi.bundleversion.BundleVersionInfo;
   40   import org.apache.sling.commons.osgi.bundleversion.FileBundleVersionInfo;
   41   
   42   /**
   43    * The <code>Loader</code> class provides utility methods for the actual
   44    * launchers to help launching the framework.
   45    */
   46   public class Loader {
   47   
   48       /**
   49        * The Sling home folder set by the constructor
   50        */
   51       private final File slingHome;
   52   
   53       /**
   54        * Creates a loader instance to load from the given Sling home folder.
   55        * Besides ensuring the existence of the Sling home folder, the constructor
   56        * also removes all but the most recent launcher JAR files from the Sling
   57        * home folder (thus cleaning up from previous upgrades).
   58        *
   59        * @param slingHome The Sling home folder. If this is <code>null</code> the
   60        *            default value {@link SharedConstants#SLING_HOME_DEFAULT} is
   61        *            assumed.
   62        * @throws IllegalArgumentException If the Sling home folder exists but is
   63        *             not a directory or if the Sling home folder cannot be
   64        *             created.
   65        */
   66       public Loader(final String slingHome) throws IllegalArgumentException {
   67           this.slingHome = getSlingHomeFile(slingHome);
   68           removeOldLauncherJars();
   69       }
   70   
   71       /**
   72        * Creates an URLClassLoader from a _launcher JAR_ file in the given
   73        * slingHome directory and loads and returns the launcher class identified
   74        * by the launcherClassName.
   75        *
   76        * @param launcherClassName The fully qualified name of a class implementing
   77        *            the Launcher interface. This class must have a public
   78        *            constructor taking no arguments.
   79        * @return the Launcher instance loaded from the newly created classloader
   80        * @throws NullPointerException if launcherClassName is null
   81        * @throws IllegalArgumentException if the launcherClassName cannot be
   82        *             instantiated. The cause of the failure is contained as the
   83        *             cause of the exception.
   84        */
   85       public Object loadLauncher(String launcherClassName) {
   86   
   87           final File launcherJarFile = getLauncherJarFile();
   88           info("Loading launcher class " + launcherClassName + " from " + launcherJarFile.getName());
   89           if (!launcherJarFile.canRead()) {
   90               throw new IllegalArgumentException("Sling Launcher JAR "
   91                   + launcherJarFile + " is not accessible");
   92           }
   93   
   94           final ClassLoader loader;
   95           try {
   96               loader = new LauncherClassLoader(launcherJarFile);
   97           } catch (MalformedURLException e) {
   98               throw new IllegalArgumentException(
   99                   "Cannot create an URL from the Sling Launcher JAR path name", e);
  100           }
  101   
  102           try {
  103               final Class<?> launcherClass = loader.loadClass(launcherClassName);
  104               return launcherClass.newInstance();
  105           } catch (ClassNotFoundException cnfe) {
  106               throw new IllegalArgumentException("Cannot find class "
  107                   + launcherClassName + " in " + launcherJarFile, cnfe);
  108           } catch (InstantiationException e) {
  109               throw new IllegalArgumentException(
  110                   "Cannot instantiate launcher class " + launcherClassName, e);
  111           } catch (IllegalAccessException e) {
  112               throw new IllegalArgumentException(
  113                   "Cannot access constructor of class " + launcherClassName, e);
  114           }
  115       }
  116   
  117       /**
  118        * Tries to remove as many traces of class loaded by the framework from the
  119        * Java VM as possible. Most notably the following traces are removed:
  120        * <ul>
  121        * <li>JavaBeans property caches
  122        * <li>Close the Launcher Jar File (if opened by the platform)
  123        * </ul>
  124        * <p>
  125        * This method must be called when the notifier is called.
  126        *
  127        */
  128       public void cleanupVM() {
  129   
  130           // ensure the JavaBeans introspector lets go of any classes it
  131           // may haved cached after introspection
  132           Introspector.flushCaches();
  133   
  134           // if sling home is set, check whether we have to close the
  135           // launcher JAR JarFile, which might be cached in the platform
  136           closeLauncherJarFile(getLauncherJarFile());
  137       }
  138   
  139       /**
  140        * Copies the contents of the launcher JAR as indicated by the URL to the
  141        * sling home directory. If the existing file is is a more recent bundle version 
  142        * than the supplied launcher JAR file, it is is not replaced.
  143        *
  144        * @return <code>true</code> if the launcher JAR file has been installed or
  145        *         updated, <code>false</code> otherwise.
  146        * @throws IOException If an error occurrs transferring the contents
  147        */
  148       public boolean installLauncherJar(URL launcherJar) throws IOException {
  149           final File currentLauncherJarFile = getLauncherJarFile();
  150   
  151           // Copy the new launcher jar to a temporary file, and
  152           // extract bundle version info
  153           final URLConnection launcherJarConn = launcherJar.openConnection();
  154           launcherJarConn.setUseCaches(false);
  155           final File tmp = new File(slingHome, "Loader_tmp_" + System.currentTimeMillis() + SharedConstants.LAUNCHER_JAR_REL_PATH);
  156           spool(launcherJarConn.getInputStream(), tmp);
  157           final FileBundleVersionInfo newVi = new FileBundleVersionInfo(tmp);
  158           boolean installNewLauncher = true;
  159           
  160           try {
  161               if(!newVi.isBundle()) {
  162                   throw new IllegalArgumentException("New launcher jar is not a bundle, cannot get version info:" + launcherJar);
  163               }
  164               
  165               // Compare versions to decide whether to use the existing or new launcher jar
  166               if (currentLauncherJarFile.exists()) {
  167                   final FileBundleVersionInfo currentVi = new FileBundleVersionInfo(currentLauncherJarFile);
  168                   if(!currentVi.isBundle()) {
  169                       throw new IllegalArgumentException("Existing launcher jar is not a bundle, cannot get version info:" 
  170                               + currentLauncherJarFile.getAbsolutePath());
  171                   }
  172   
  173                   String info = null;
  174                   if(currentVi.compareTo(newVi) == 0) {
  175                       info = "up to date";
  176                       installNewLauncher = false;
  177                   } else if(currentVi.compareTo(newVi) > 0) {
  178                       info = "more recent than ours";
  179                       installNewLauncher = false;
  180                   }
  181                   
  182                   if(info != null) {
  183                       info("Existing launcher is " + info + ", using it: " 
  184                               + getBundleInfo(currentVi) + " (" + currentLauncherJarFile.getName() + ")");
  185                   }
  186               }
  187   
  188               if(installNewLauncher) {
  189                   final File f = new File(tmp.getParentFile(), SharedConstants.LAUNCHER_JAR_REL_PATH + "." + System.currentTimeMillis());
  190                   if(!tmp.renameTo(f)) {
  191                       throw new IllegalStateException("Failed to rename " + tmp.getName() + " to " + f.getName());
  192                   }
  193                   info("Installing new launcher: " + launcherJar  + ", " + getBundleInfo(newVi) + " (" + f.getName() + ")");
  194               }
  195           } finally {
  196               if(tmp.exists()) {
  197                   tmp.delete();
  198               }
  199           }
  200   
  201           return installNewLauncher;
  202       }
  203       
  204       /** Return relevant bundle version info for logging */
  205       static String getBundleInfo(BundleVersionInfo<?> v) {
  206           final StringBuilder sb = new StringBuilder();
  207           sb.append(v.getVersion());
  208           if(v.isSnapshot()) {
  209               sb.append(", Last-Modified:");
  210               sb.append(new Date(v.getBundleLastModified()));
  211           }
  212           return sb.toString();
  213       }
  214   
  215       /**
  216        * Removes old candidate launcher JAR files leaving the most recent one as
  217        * the launcher JAR file to use on next Sling startup.
  218        *
  219        * @param slingHome The Sling home directory location containing the
  220        *            candidate launcher JAR files.
  221        */
  222       private void removeOldLauncherJars() {
  223           final File[] launcherJars = getLauncherJarFiles();
  224           if (launcherJars != null && launcherJars.length > 0) {
  225               
  226               // Remove all files except current one
  227               final File current = getLauncherJarFile();
  228               for(File f : launcherJars) {
  229                   if(f.getAbsolutePath().equals(current.getAbsolutePath())) {
  230                       continue;
  231                   }
  232                   String versionInfo = null;
  233                   try {
  234                       FileBundleVersionInfo vi = new FileBundleVersionInfo(f);
  235                       versionInfo = getBundleInfo(vi);
  236                   } catch(IOException ignored) {
  237                   }
  238                   info("Deleting obsolete launcher jar: " + f.getName() + ", " + versionInfo);
  239                   f.delete();
  240               }
  241   
  242               // And ensure the current file has the standard launcher name
  243               if (!SharedConstants.LAUNCHER_JAR_REL_PATH.equals(current.getName())) {
  244                   info("Renaming current launcher jar " + current.getName() 
  245                           + " to " + SharedConstants.LAUNCHER_JAR_REL_PATH);
  246                   File launcherFileName = new File(
  247                           current.getParentFile(),
  248                       SharedConstants.LAUNCHER_JAR_REL_PATH);
  249                   current.renameTo(launcherFileName);
  250               }
  251           }
  252       }
  253   
  254       /**
  255        * Spools the contents of the input stream to the given file replacing the
  256        * contents of the file with the contents of the input stream. When this
  257        * method returns, the input stream is guaranteed to be closed.
  258        *
  259        * @throws IOException If an error occurrs reading or writing the input
  260        *             stream contents.
  261        */
  262       public static void spool(InputStream ins, File destFile) throws IOException {
  263           OutputStream out = null;
  264           try {
  265               out = new FileOutputStream(destFile);
  266               byte[] buf = new byte[8192];
  267               int rd;
  268               while ((rd = ins.read(buf)) >= 0) {
  269                   out.write(buf, 0, rd);
  270               }
  271           } finally {
  272               if (ins != null) {
  273                   try {
  274                       ins.close();
  275                   } catch (IOException ignore) {
  276                   }
  277               }
  278               if (out != null) {
  279                   try {
  280                       out.close();
  281                   } catch (IOException ignore) {
  282                   }
  283               }
  284           }
  285       }
  286   
  287       // ---------- internal helper
  288   
  289       /**
  290        * Returns a <code>File</code> object representing the Launcher JAR file
  291        * found in the sling home folder.
  292        */
  293       private File getLauncherJarFile() {
  294           File result = null;
  295           final File[] launcherJars = getLauncherJarFiles();
  296           if (launcherJars == null || launcherJars.length == 0) {
  297   
  298               // return a non-existing file naming the desired primary name
  299               result = new File(slingHome,
  300                   SharedConstants.LAUNCHER_JAR_REL_PATH);
  301   
  302           } else {
  303               // last file is the most recent one, use it
  304               result = launcherJars[launcherJars.length - 1];
  305           }
  306   
  307           return result;
  308       }
  309   
  310       /**
  311        * Returns all files in the <code>slingHome</code> directory which may be
  312        * considered as launcher JAR files, sorted based on their bundle version
  313        * information, most recent last. These files all start with the
  314        * {@link SharedConstants#LAUNCHER_JAR_REL_PATH}. This list may be empty if
  315        * the launcher JAR file has not been installed yet.
  316        *
  317        * @param slingHome The sling home directory where the launcher JAR files
  318        *            are stored
  319        * @return The list of candidate launcher JAR files, which may be empty.
  320        *         <code>null</code> is returned if an IO error occurs trying to
  321        *         list the files.
  322        */
  323       private File[] getLauncherJarFiles() {
  324           // Get list of files with names starting with our prefix
  325           final File[] rawList = slingHome.listFiles(new FileFilter() {
  326               public boolean accept(File pathname) {
  327                   return pathname.isFile()
  328                       && pathname.getName().startsWith(
  329                           SharedConstants.LAUNCHER_JAR_REL_PATH);
  330               }
  331           });
  332           
  333           // Keep only those which have valid Bundle headers, and
  334           // sort them according to the bundle version numbers
  335           final List<FileBundleVersionInfo> list = new ArrayList<FileBundleVersionInfo>();
  336           for(File f : rawList) {
  337               FileBundleVersionInfo fvi = null;
  338               try {
  339                   fvi = new FileBundleVersionInfo(f);
  340               } catch(IOException ioe) {
  341                   // Cannot read bundle info from jar file - should never happen??
  342                   throw new IllegalStateException("Cannot read bundle information from loader file " + f.getAbsolutePath());
  343               }
  344               if(fvi.isBundle()) {
  345                   list.add(fvi);
  346               }
  347           }
  348           Collections.sort(list);
  349           final File [] result = new File[list.size()];
  350           int i = 0;
  351           for(FileBundleVersionInfo fvi : list) {
  352               result[i++] = fvi.getSource();
  353           }
  354           return result;
  355       }
  356   
  357       /**
  358        * Returns the <code>slingHome</code> path as a directory. If the directory
  359        * does not exist it is created. If creation fails or if
  360        * <code>slingHome</code> exists but is not a directory a
  361        * <code>IllegalArgumentException</code> is thrown.
  362        *
  363        * @param slingHome The sling home directory where the launcher JAR files
  364        *            are stored
  365        * @return The Sling home directory
  366        * @throws IllegalArgumentException if <code>slingHome</code> exists and is
  367        *             not a directory or cannot be created as a directory.
  368        */
  369       private static File getSlingHomeFile(String slingHome) {
  370           if (slingHome == null) {
  371               slingHome = SharedConstants.SLING_HOME_DEFAULT;
  372           }
  373   
  374           File slingDir = new File(slingHome).getAbsoluteFile();
  375           if (slingDir.exists()) {
  376               if (!slingDir.isDirectory()) {
  377                   throw new IllegalArgumentException("Sling Home " + slingDir
  378                       + " exists but is not a directory");
  379               }
  380           } else if (!slingDir.mkdirs()) {
  381               throw new IllegalArgumentException("Sling Home " + slingDir
  382                   + " cannot be created as a directory");
  383           }
  384   
  385           return slingDir;
  386       }
  387   
  388       private static void closeLauncherJarFile(final File launcherJar) {
  389           try {
  390               final URI launcherJarUri = launcherJar.toURI();
  391               final URL launcherJarRoot = new URL("jar:" + launcherJarUri + "!/");
  392               final URLConnection conn = launcherJarRoot.openConnection();
  393               if (conn instanceof JarURLConnection) {
  394                   final JarFile jarFile = ((JarURLConnection) conn).getJarFile();
  395                   jarFile.close();
  396               }
  397           } catch (Exception e) {
  398               // better logging here
  399           }
  400       }
  401   
  402       /** Meant to be overridden to display or log info */
  403       protected void info(String msg) {
  404       }
  405   }

Save This Page
Home » org.apache.sling.launchpad.base-2.2.0-source-release » org.apache.sling.launchpad.base.shared » [javadoc | source]