Save This Page
Home » org.apache.sling.launchpad.base-2.2.0-source-release » org.apache.sling.launchpad.base.impl » [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.impl;
   20   
   21   import java.io.File;
   22   import java.io.FileFilter;
   23   import java.io.FileInputStream;
   24   import java.io.FileNotFoundException;
   25   import java.io.FileOutputStream;
   26   import java.io.IOException;
   27   import java.io.InputStream;
   28   import java.io.OutputStream;
   29   import java.net.URL;
   30   import java.net.URLClassLoader;
   31   import java.util.Dictionary;
   32   import java.util.HashMap;
   33   import java.util.Iterator;
   34   import java.util.LinkedList;
   35   import java.util.List;
   36   import java.util.Map;
   37   import java.util.jar.JarInputStream;
   38   import java.util.jar.Manifest;
   39   
   40   import org.apache.felix.framework.Logger;
   41   import org.apache.sling.launchpad.base.impl.bootstrapcommands.BootstrapCommandFile;
   42   import org.apache.sling.launchpad.base.shared.SharedConstants;
   43   import org.osgi.framework.Bundle;
   44   import org.osgi.framework.BundleActivator;
   45   import org.osgi.framework.BundleContext;
   46   import org.osgi.framework.BundleException;
   47   import org.osgi.framework.Constants;
   48   import org.osgi.framework.FrameworkEvent;
   49   import org.osgi.framework.FrameworkListener;
   50   import org.osgi.framework.ServiceReference;
   51   import org.osgi.framework.Version;
   52   import org.osgi.service.startlevel.StartLevel;
   53   
   54   /**
   55    * The <code>BootstrapInstaller</code> class is installed into the OSGi
   56    * framework as an activator to be called when the framework is starting up.
   57    * Upon startup all bundles from the {@link #PATH_CORE_BUNDLES} and the
   58    * {@link #PATH_BUNDLES} location are checked whether they are already installed
   59    * or not. If they are not installed, they are installed, their start level set
   60    * to 1 and started. Any bundle already installed is not installed again and
   61    * will also not be started here.
   62    */
   63   class BootstrapInstaller implements BundleActivator, FrameworkListener {
   64   
   65       /**
   66        * The Bundle location scheme (protocol) used for bundles installed by this
   67        * activator (value is "slinginstall:"). The path part of the Bundle
   68        * location of Bundles installed by this class is the name (without the
   69        * path) of the resource from which the Bundle was installed.
   70        */
   71       private static final String SCHEME = "slinginstall:";
   72   
   73       /**
   74        * The root location in which the bundles are looked up for installation
   75        * (value is "resources/").
   76        */
   77       private static final String PATH_RESOURCES = "resources/";
   78   
   79       /**
   80        * The path of startup bundles in the sling home
   81        */
   82       private static final String PATH_STARTUP = "startup/";
   83   
   84       /**
   85        * The location of the core Bundles (value is "resources/corebundles").
   86        * These bundles are installed at startlevel
   87        * {@link #STARTLEVEL_CORE_BUNDLES}.
   88        * <p>
   89        * This location is deprecated, instead these core bundles should be located
   90        * in <code>resources/bundles/1</code>.
   91        */
   92       static final String PATH_CORE_BUNDLES = PATH_RESOURCES + "corebundles";
   93   
   94       /**
   95        * The location the additional Bundles (value is "resources/bundles"). These
   96        * Bundles are installed after the {@link #PATH_CORE_BUNDLES core Bundles}.
   97        */
   98       static final String PATH_BUNDLES = PATH_RESOURCES + "bundles";
   99   
  100       /**
  101        * The possible file extensions for a bundle archive file.
  102        */
  103       private static final String[] BUNDLE_EXTENSIONS = { ".jar", ".war" };
  104   
  105       /**
  106        * The header which contains the bundle's last modified date.
  107        */
  108       static final String BND_LAST_MODIFIED_HEADER = "Bnd-LastModified";
  109   
  110       /**
  111        * The start level to be assigned to bundles found in the (old style)
  112        * {@link #PATH_CORE_BUNDLES resources/corebundles} location (value is 1).
  113        */
  114       private static final int STARTLEVEL_CORE_BUNDLES = 1;
  115   
  116       /**
  117        * The start level to be assigned to bundles found in the (old style)
  118        * {@link #PATH_BUNDLES resources/bundles} location (value is 0).
  119        */
  120       private static final int STARTLEVEL_BUNDLES = 0;
  121   
  122       /**
  123        * The marker start level indicating the location of the bundle cannot be
  124        * resolved to a valid start level (value is -1).
  125        */
  126       private static final int STARTLEVEL_NONE = -1;
  127   
  128       /** The data file which works as a marker to detect the first startup. */
  129       private static final String DATA_FILE = "bootstrapinstaller.ser";
  130   
  131       /**
  132        * The name of the bootstrap commands file
  133        */
  134       private static final String BOOTSTRAP_CMD_FILENAME = "sling_bootstrap.txt";
  135   
  136       /**
  137        * The {@link Logger} use for logging messages during installation and
  138        * startup.
  139        */
  140       private final Logger logger;
  141   
  142       /**
  143        * The {@link ResourceProvider} used to access the Bundle files to
  144        * install.
  145        */
  146       private final ResourceProvider resourceProvider;
  147   
  148       private BundleContext bundleContext;
  149   
  150       /**
  151        * The OSGi start level into which the framework is taken by the
  152        * {@link #frameworkEvent(FrameworkEvent)} method when the framework has
  153        * reached the originally specified start level.
  154        * <p>
  155        * If this value is smaller than 1 the framework is restarted. This is
  156        * particularly the case if the {@link #start(BundleContext)} method causes
  157        * the update of an installed framework extension bundle.
  158        * <p>
  159        * This value is preset by the
  160        * {@link #BootstrapInstaller(Logger, ResourceProvider, Map)} constructor to
  161        * the value set in the <code>org.osgi.framework.startlevel.beginning</code>
  162        * property of the supplied map.
  163        */
  164       private int targetStartLevel;
  165   
  166       BootstrapInstaller(Logger logger, ResourceProvider resourceProvider,
  167               Map<String, String> props) {
  168           this.logger = logger;
  169           this.resourceProvider = resourceProvider;
  170           this.targetStartLevel = getStartLevel(props);
  171       }
  172   
  173       //---------- BundleActivator interface
  174   
  175       /**
  176        * https://issues.apache.org/jira/browse/SLING-922
  177        * Handles the initial detection and installation of bundles into
  178        * the Felix OSGi running in Sling
  179        *
  180        * Process:
  181        * 1) Copy all bundles from enclosed resources (jar/war) to
  182        *   ${sling.home}/startup. This gives something like
  183        *   ${sling.home}/startup/0, /1, /10, /15, ...
  184        *   Existing files are only replaced if the files
  185        *   enclosed in the Sling launchpad jar/war file are newer.
  186        * 2) Scan ${sling.home}/startup for bundles to install
  187        *   in the same way as today the enclosed resources
  188        *   are scanned directly.
  189        *   So you could place your bundles in that structure and get them installed
  190        *   at the requested start level (0 being "default bundle start level").
  191        */
  192       public void start(final BundleContext context) throws Exception {
  193   
  194           // prepare for further startup after initial startup
  195           // see the frameworkEvent method for details
  196           this.bundleContext = context;
  197           this.bundleContext.addFrameworkListener(this);
  198   
  199           // get the startup location in sling home
  200           String slingHome = context.getProperty(SharedConstants.SLING_HOME);
  201           File slingStartupDir = getSlingStartupDir(slingHome);
  202   
  203           // execute bootstrap commands, if needed
  204           BootstrapCommandFile cmd = new BootstrapCommandFile(logger, new File(slingHome, BOOTSTRAP_CMD_FILENAME));
  205           cmd.execute(context);
  206   
  207           boolean shouldInstall = false;
  208   
  209           // see if the loading of bundles from the package is forced
  210           String fpblString = context.getProperty(SharedConstants.FORCE_PACKAGE_BUNDLE_LOADING);
  211           if (Boolean.valueOf(fpblString)) {
  212               shouldInstall = true;
  213           } else {
  214               shouldInstall = !isAlreadyInstalled(context, slingStartupDir);
  215           }
  216   
  217           if (shouldInstall) {
  218               // only run the deployment package stuff and war/jar copies when this war/jar is new/changed
  219   
  220               // register deployment package support
  221               try {
  222                   final DeploymentPackageInstaller dpi = new DeploymentPackageInstaller(
  223                       context, logger, resourceProvider);
  224                   context.addFrameworkListener(dpi);
  225                   context.addServiceListener(dpi, "(" + Constants.OBJECTCLASS
  226                       + "=" + DeploymentPackageInstaller.DEPLOYMENT_ADMIN + ")");
  227               } catch (Throwable t) {
  228                   logger.log(
  229                       Logger.LOG_WARNING,
  230                       "Cannot register Deployment Admin support, continuing without",
  231                       t);
  232               }
  233   
  234               // see if the loading of bundles from the package is disabled
  235               String dpblString = context.getProperty(SharedConstants.DISABLE_PACKAGE_BUNDLE_LOADING);
  236               Boolean disablePackageBundleLoading = Boolean.valueOf(dpblString);
  237   
  238               if (disablePackageBundleLoading) {
  239                   logger.log(Logger.LOG_INFO, "Package bundle loading is disabled so no bundles will be installed from the resources location in the sling jar/war");
  240               } else {
  241                   // get the bundles out of the jar/war and copy them to the startup location
  242                   Iterator<String> resources = resourceProvider.getChildren(PATH_BUNDLES);
  243                   while (resources.hasNext()) {
  244                       String path = resources.next();
  245                       // only consider folders
  246                       if (path.endsWith("/")) {
  247   
  248                           // cut off trailing slash
  249                           path = path.substring(0, path.length() - 1);
  250   
  251                           // calculate the startlevel of bundles contained
  252                           int startLevel = getStartLevel(path);
  253                           if (startLevel != STARTLEVEL_NONE) {
  254                               copyBundles(slingStartupDir, path, startLevel);
  255                           }
  256                       }
  257                   }
  258   
  259                   // copy old-style core bundles
  260                   copyBundles(slingStartupDir, PATH_CORE_BUNDLES, STARTLEVEL_CORE_BUNDLES);
  261   
  262                   // copy old-style bundles
  263                   copyBundles(slingStartupDir, PATH_BUNDLES, STARTLEVEL_BUNDLES);
  264   
  265                   // done with copying at this point
  266               }
  267   
  268               // get the set of all existing (installed) bundles by symbolic name
  269               Bundle[] bundles = context.getBundles();
  270               Map<String, Bundle> bySymbolicName = new HashMap<String, Bundle>();
  271               for (int i = 0; i < bundles.length; i++) {
  272                   bySymbolicName.put(bundles[i].getSymbolicName(), bundles[i]);
  273               }
  274   
  275               // holds the bundles we install during this processing
  276               List<Bundle> installed = new LinkedList<Bundle>();
  277   
  278               // get all bundles from the startup location and install them
  279               boolean requireRestart = installBundles(slingStartupDir, context, bySymbolicName, installed);
  280   
  281               // start all the newly installed bundles (existing bundles are not started if they are stopped)
  282               startBundles(installed);
  283   
  284               // mark everything installed
  285               markInstalled(context, slingStartupDir);
  286   
  287               // due to the upgrade of a framework extension bundle, the framework
  288               // has to be restarted. For this reason, set the target start level
  289               // to a negative value.
  290               if (requireRestart) {
  291                   logger.log(
  292                       Logger.LOG_INFO,
  293                       "Framework extension(s) have been updated, restarting framework after startup has completed");
  294   
  295                   targetStartLevel = -1;
  296               }
  297           }
  298       }
  299   
  300       /** Nothing to be done on stop */
  301       public void stop(BundleContext context) {
  302           this.bundleContext = null;
  303       }
  304   
  305       //---------- Framework Listener
  306   
  307       /**
  308        * Called whenever a framework event is taking place. This method only cares
  309        * for the framework event emitted once the framework startup has completed.
  310        * Once the framework startup has completed, this method takes further
  311        * actions (besides unregistering as a framework listener):
  312        * <ul>
  313        * <li>If bundle installation in the {@link #start(BundleContext)} method
  314        * included an update of a framework extension fragment bundle, the
  315        * framework has to be restarted. This is effectuated by calling the
  316        * <code>Bundle.update()</code> method on the system bundle.</li>
  317        * <li>If a restart is not required, the StartLevel service is instructed to
  318        * raise the framework start level to the value requested by the framework
  319        * launcher.</li>
  320        * </ul>
  321        */
  322       public void frameworkEvent(FrameworkEvent event) {
  323           if (event.getType() == FrameworkEvent.STARTED) {
  324   
  325               // don't care for further events
  326               this.bundleContext.removeFrameworkListener(this);
  327   
  328               if (targetStartLevel < 1) {
  329   
  330                   // restart
  331                   logger.log(Logger.LOG_INFO,
  332                       "Restarting framework to resolve new framework extension(s)");
  333                   try {
  334                       bundleContext.getBundle(0).update();
  335                   } catch (BundleException be) {
  336                       logger.log(
  337                           Logger.LOG_ERROR,
  338                           "Failed restarting to resolve new framework extension(s)",
  339                           be);
  340                   }
  341   
  342               } else {
  343   
  344                   // raise start level to the desired target
  345                   ServiceReference sr = bundleContext.getServiceReference(StartLevel.class.getName());
  346                   if (sr != null) {
  347                       StartLevel sl = (StartLevel) bundleContext.getService(sr);
  348                       try {
  349                           logger.log(Logger.LOG_INFO, "Setting start level to "
  350                               + targetStartLevel);
  351                           sl.setStartLevel(targetStartLevel);
  352                       } finally {
  353                           bundleContext.ungetService(sr);
  354                       }
  355                   } else {
  356                       logger.log(Logger.LOG_WARNING,
  357                           "StartLevel service not available, will not set the start level");
  358                   }
  359   
  360               }
  361           }
  362       }
  363   
  364       //---------- Startup folder maintenance
  365   
  366       /**
  367        * Get the sling startup directory (or create it) in the sling home if possible
  368        * @param slingHome the path to the sling home
  369        * @return the sling startup directory
  370        * @throws IllegalStateException if the sling home or startup directories cannot be created/accessed
  371        */
  372       private File getSlingStartupDir(String slingHome) {
  373           if (isBlank(slingHome)) {
  374               throw new IllegalStateException("Fatal error in bootstrap: Cannot get the "+SharedConstants.SLING_HOME+" value: " + slingHome);
  375           }
  376           File slingHomeDir = new File(slingHome).getAbsoluteFile();
  377           if (! slingHomeDir.exists()
  378                   || ! slingHomeDir.canRead()
  379                   || ! slingHomeDir.canWrite()
  380                   || ! slingHomeDir.isDirectory()) {
  381               throw new IllegalStateException("Fatal error in bootstrap: Cannot find accessible existing "
  382                       +SharedConstants.SLING_HOME+" directory: " + slingHomeDir);
  383           }
  384           File slingHomeStartupDir = getOrCreateDirectory(slingHomeDir, PATH_STARTUP);
  385           return slingHomeStartupDir;
  386       }
  387   
  388       /**
  389        * Get or create a sub-directory from an existing parent
  390        * @param parentDir the parent directory
  391        * @param subDirName the name of the sub-directory
  392        * @return the sub-directory
  393        * @throws IllegalStateException if directory cannot be created/accessed
  394        */
  395       private File getOrCreateDirectory(File parentDir, String subDirName) {
  396           File slingHomeStartupDir = new File(parentDir, subDirName).getAbsoluteFile();
  397           if ( slingHomeStartupDir.exists() ) {
  398               if (! slingHomeStartupDir.isDirectory()
  399                       || ! parentDir.canRead()
  400                       || ! parentDir.canWrite() ) {
  401                   throw new IllegalStateException("Fatal error in bootstrap: Cannot find accessible existing "
  402                           +SharedConstants.SLING_HOME+PATH_STARTUP+" directory: " + slingHomeStartupDir);
  403               }
  404           } else if (! slingHomeStartupDir.mkdirs() ) {
  405               throw new IllegalStateException("Sling Home " + slingHomeStartupDir + " cannot be created as a directory");
  406           }
  407           return slingHomeStartupDir;
  408       }
  409   
  410       /**
  411        * Copies the bundles from the given parent location in the jar/war
  412        * to the startup directory in the sling.home based on the startlevel
  413        * e.g. {sling.home}/startup/{startLevel}
  414        */
  415       private void copyBundles(File slingStartupDir, String parent, int startLevel) {
  416   
  417           // set default start level
  418           if (startLevel < 0) {
  419               startLevel = 0;
  420           }
  421           // this will be set and created on demand
  422           File startUpLevelDir = null;
  423   
  424           Iterator<String> res = resourceProvider.getChildren(parent);
  425           while (res.hasNext()) {
  426               // path to the next resource
  427               String path = res.next();
  428   
  429               if (isBundle(path)) {
  430                   // try to access the bundle file, ignore if not possible
  431                   InputStream ins = resourceProvider.getResourceAsStream(path);
  432                   if (ins == null) {
  433                       continue;
  434                   }
  435   
  436                   // ensure we have a directory for the startlevel only when
  437                   // needed
  438                   if (startUpLevelDir == null) {
  439                       startUpLevelDir = getOrCreateDirectory(slingStartupDir,
  440                           String.valueOf(startLevel));
  441                   }
  442   
  443                   // copy over the bundle based on the startlevel
  444                   String bundleFileName = extractFileName(path);
  445                   File bundleFile = new File(startUpLevelDir, bundleFileName);
  446                   try {
  447                       copyStreamToFile(ins, bundleFile);
  448                   } catch (IOException e) {
  449                       // should this fail here or just log a warning?
  450                       throw new RuntimeException("Failure copying file from "
  451                           + path + " to startup dir (" + startUpLevelDir
  452                           + ") and name (" + bundleFileName + "): " + e, e);
  453                   }
  454               }
  455           }
  456       }
  457   
  458       /**
  459        * Determine if a path could be a bundle based on its extension.
  460        *
  461        * @param path the path to the file
  462        * @return true if the path could be a bundle
  463        */
  464       static boolean isBundle(String path) {
  465           for (String extension : BUNDLE_EXTENSIONS) {
  466               if (path.endsWith(extension)) {
  467                   return true;
  468               }
  469           }
  470           return false;
  471       }
  472   
  473       /**
  474        * Copies a stream from the resource (jar/war) to a file
  475        * @param fromStream
  476        * @param toFile
  477        */
  478       static void copyStreamToFile(InputStream fromStream, File toFile) throws IOException {
  479           if (fromStream == null || toFile == null) {
  480               throw new IllegalArgumentException("fromStream and toFile must not be null");
  481           }
  482           if (! toFile.exists()) {
  483               toFile.createNewFile();
  484           }
  485           // overwrite
  486           OutputStream out = new FileOutputStream(toFile);
  487           try {
  488               byte[] buf = new byte[1024];
  489               int len;
  490               while ((len = fromStream.read(buf)) > 0) {
  491                   out.write(buf, 0, len);
  492               }
  493           } finally {
  494               out.close();
  495           }
  496       }
  497   
  498       /**
  499        * Install the Bundles from files found in startup directory under the
  500        * level directories, this will only install bundles which are new or updated
  501        * and will skip over them otherwise
  502        *
  503        * @param context The <code>BundleContext</code> used to install the new Bundles.
  504        * @param currentBundles The currently installed Bundles indexed by their
  505        *            Bundle location.
  506        * @param parent The path to the location in which to look for bundle files to
  507        *            install. Only resources whose name ends with one of the known bundle extensions are
  508        *            considered for installation.
  509        * @param installed The list of Bundles installed by this method. Each
  510        *            Bundle successfully installed is added to this list.
  511        *
  512        * @return <code>true</code> if a system bundle fragment was updated which
  513        *      requires the framework to restart.
  514        */
  515       private boolean installBundles(File slingStartupDir,
  516               BundleContext context, Map<String, Bundle> currentBundles,
  517               List<Bundle> installed) {
  518   
  519           // get the start level service (if possible) so we can set the initial start level
  520           ServiceReference ref = context.getServiceReference(StartLevel.class.getName());
  521           StartLevel startLevelService = (ref != null)
  522                   ? (StartLevel) context.getService(ref)
  523                   : null;
  524   
  525           boolean requireRestart = false;
  526           try {
  527               File[] directories = slingStartupDir.listFiles(DIRECTORY_FILTER);
  528               for (File levelDir : directories) {
  529                   // get startlevel from dir name
  530                   String dirName = levelDir.getName();
  531                   int startLevel;
  532                   try {
  533                       startLevel = Integer.decode(dirName);
  534                   } catch (NumberFormatException e) {
  535                       startLevel = 0;
  536                   }
  537   
  538                   // iterate through all files in the startlevel dir
  539                   File[] bundleFiles = levelDir.listFiles(BUNDLE_FILE_FILTER);
  540                   for (File bundleFile : bundleFiles) {
  541                       requireRestart |= installBundle(bundleFile, startLevel,
  542                           context, currentBundles, installed, startLevelService);
  543                   }
  544               }
  545   
  546           } finally {
  547               // release the start level service
  548               if (ref != null) {
  549                   context.ungetService(ref);
  550               }
  551           }
  552   
  553           return requireRestart;
  554       }
  555   
  556       /**
  557        * @param bundleJar the jar file for the bundle to install
  558        * @param startLevel the start level to use for this bundle
  559        * @param context The <code>BundleContext</code> used to install the new Bundles.
  560        * @param currentBundles The currently installed Bundles indexed by their
  561        *            Bundle location.
  562        * @param installed The list of Bundles installed by this method. Each
  563        *            Bundle successfully installed is added to this list.
  564        * @param startLevelService the service which sets the start level
  565        *
  566        * @return <code>true</code> if a system bundle fragment was updated which
  567        *      requires the framework to restart.
  568        */
  569       private boolean installBundle(File bundleJar, int startLevel,
  570               BundleContext context, Map<String, Bundle> currentBundles,
  571               List<Bundle> installed, StartLevel startLevelService) {
  572           // get the manifest for the bundle information
  573           Manifest manifest = getManifest(bundleJar);
  574           if (manifest == null) {
  575               logger.log(Logger.LOG_ERROR, "Ignoring " + bundleJar
  576                   + ": Cannot read manifest");
  577               return false; // SHORT CIRCUIT
  578           }
  579   
  580           // ensure a symbolic name in the jar file
  581           String symbolicName = getBundleSymbolicName(manifest);
  582           if (symbolicName == null) {
  583               logger.log(Logger.LOG_ERROR, "Ignoring " + bundleJar
  584                   + ": Missing " + Constants.BUNDLE_SYMBOLICNAME
  585                   + " in manifest");
  586               return false; // SHORT CIRCUIT
  587           }
  588   
  589           // check for an installed Bundle with the symbolic name
  590           Bundle installedBundle = currentBundles.get(symbolicName);
  591           if (ignore(installedBundle, manifest)) {
  592               logger.log(Logger.LOG_INFO, "Ignoring " + bundleJar
  593                   + ": More recent version already installed");
  594               return false; // SHORT CIRCUIT
  595           }
  596   
  597           // try to access the JAR file, ignore if not possible
  598           InputStream ins;
  599           try {
  600               ins = new FileInputStream(bundleJar);
  601           } catch (FileNotFoundException e) {
  602               return false; // SHORT CIRCUIT
  603           }
  604   
  605           final boolean requireRestart;
  606           if (installedBundle != null) {
  607   
  608               // if the installed bundle is an extension fragment we have to
  609               // restart the framework after completing the installation
  610               // or upgrade of all new bundles
  611               requireRestart = isSystemBundleFragment(installedBundle);
  612   
  613               try {
  614                   installedBundle.update(ins);
  615                   logger.log(Logger.LOG_INFO, "Bundle "
  616                       + installedBundle.getSymbolicName()
  617                       + " updated from " + bundleJar);
  618               } catch (BundleException be) {
  619                   logger.log(Logger.LOG_ERROR, "Bundle update from "
  620                       + bundleJar + " failed", be);
  621               }
  622   
  623           } else {
  624   
  625               // restart is not required for any bundle installation at this
  626               // stage
  627               requireRestart = false;
  628   
  629               // install the JAR file as a bundle
  630               String path = bundleJar.getPath();
  631               String location = SCHEME
  632                   + path.substring(path.lastIndexOf('/') + 1);
  633               try {
  634                   Bundle theBundle = context.installBundle(location, ins);
  635                   logger.log(Logger.LOG_INFO, "Bundle "
  636                       + theBundle.getSymbolicName() + " installed from "
  637                       + location);
  638   
  639                   // finally add the bundle to the list for later start
  640                   installed.add(theBundle);
  641   
  642                   // optionally set the start level
  643                   if (startLevel > 0) {
  644                       startLevelService.setBundleStartLevel(theBundle,
  645                           startLevel);
  646                   }
  647   
  648               } catch (BundleException be) {
  649                   logger.log(Logger.LOG_ERROR,
  650                       "Bundle installation from " + location + " failed", be);
  651               }
  652           }
  653   
  654           return requireRestart;
  655       }
  656   
  657       /**
  658        * Starts the Bundles in the <code>bundles</code> list. If the framework
  659        * provides an active <code>StartLevel</code> service, the start levels of
  660        * the Bundles is first set to <em>1</em>.
  661        */
  662       private void startBundles(List<Bundle> bundles) {
  663   
  664           // start all bundles
  665           for (Bundle bundle : bundles) {
  666               try {
  667                   if (!isFragment(bundle)) {
  668                       bundle.start();
  669                   }
  670               } catch (BundleException be) {
  671                   logger.log(Logger.LOG_ERROR, "Bundle "
  672                       + bundle.getSymbolicName() + " could not be started", be);
  673               }
  674           }
  675   
  676       }
  677   
  678       private int getStartLevel(String path) {
  679           String name = path.substring(path.lastIndexOf('/') + 1);
  680           try {
  681               int level = Integer.parseInt(name);
  682               if (level >= 0) {
  683                   return level;
  684               }
  685   
  686               logger.log(Logger.LOG_ERROR, "Illegal Runlevel for " + path
  687                   + ", ignoring");
  688           } catch (NumberFormatException nfe) {
  689               logger.log(Logger.LOG_INFO, "Folder " + path
  690                   + " does not denote start level, ignoring");
  691           }
  692   
  693           // no valid start level, ignore this location
  694           return STARTLEVEL_NONE;
  695       }
  696   
  697       private boolean isSystemBundleFragment(final Bundle installedBundle) {
  698           final String fragmentHeader = (String) installedBundle.getHeaders().get(
  699               Constants.FRAGMENT_HOST);
  700           return fragmentHeader != null
  701               && fragmentHeader.indexOf(Constants.EXTENSION_DIRECTIVE) > 0;
  702       }
  703   
  704       // ---------- Bundle JAR file information
  705   
  706       /**
  707        * Returns the Manifest from the JAR file in the given resource provided by
  708        * the resource provider or <code>null</code> if the resource does not
  709        * exists or is not a JAR file or has no Manifest.
  710        *
  711        * @param jarPath The path to the JAR file provided by the resource provider
  712        *            of this instance.
  713        */
  714       private Manifest getManifest(File jar) {
  715           try {
  716               InputStream ins = new FileInputStream(jar);
  717               return getManifest(ins);
  718           } catch (FileNotFoundException e) {
  719               logger.log(Logger.LOG_WARNING, "Could not get inputstream from file ("+jar+"):"+e);
  720               //throw new IllegalArgumentException("Could not get inputstream from file ("+jar+"):"+e, e);
  721           }
  722           return null;
  723       }
  724   
  725       /**
  726        * Return the manifest from a jar if it is possible to get it,
  727        * this will also handle closing out the stream
  728        *
  729        * @param ins the inputstream for the jar
  730        * @return the manifest OR null if it cannot be obtained
  731        */
  732       Manifest getManifest(InputStream ins) {
  733           try {
  734               JarInputStream jis = new JarInputStream(ins);
  735               return jis.getManifest();
  736           } catch (IOException ioe) {
  737               logger.log(Logger.LOG_ERROR, "Failed to read manifest from stream: "
  738                       + ins, ioe);
  739           } finally {
  740               try {
  741                   ins.close();
  742               } catch (IOException ignore) {
  743               }
  744           }
  745           return null;
  746       }
  747   
  748       /**
  749        * Returns the <i>Bundle-SymbolicName</i> header from the given manifest or
  750        * <code>null</code> if no such header exists.
  751        * <p>
  752        * Note that bundles are not allowed to have no symbolic name any more.
  753        * Therefore a bundle without a symbolic name header should not be
  754        * installed.
  755        *
  756        * @param manifest The Manifest from which to extract the header.
  757        */
  758       String getBundleSymbolicName(Manifest manifest) {
  759           return manifest.getMainAttributes().getValue(
  760               Constants.BUNDLE_SYMBOLICNAME);
  761       }
  762   
  763       /**
  764        * Checks whether the installed bundle is at the same version (or more
  765        * recent) than the bundle described by the given manifest.
  766        *
  767        * @param installedBundle The bundle currently installed in the framework
  768        * @param manifest The Manifest describing the bundle version potentially
  769        *            updating the installed bundle
  770        * @return <code>true</code> if the manifest does not describe a bundle with
  771        *         a higher version number.
  772        */
  773       private boolean ignore(Bundle installedBundle, Manifest manifest) {
  774   
  775           // the bundle is not installed yet, so we have to install it
  776           if (installedBundle == null) {
  777               return false;
  778           }
  779   
  780           String versionProp = manifest.getMainAttributes().getValue(
  781               Constants.BUNDLE_VERSION);
  782           Version newVersion = Version.parseVersion(versionProp);
  783   
  784           String installedVersionProp = (String) installedBundle.getHeaders().get(
  785               Constants.BUNDLE_VERSION);
  786           Version installedVersion = Version.parseVersion(installedVersionProp);
  787   
  788           // if the new version and the current version are the same, reinstall if
  789           // the version is a snapshot
  790           if (newVersion.equals(installedVersion)
  791               && installedVersionProp.endsWith("SNAPSHOT")
  792               && isNewerSnapshot(installedBundle, manifest)) {
  793               logger.log(Logger.LOG_INFO, "Forcing upgrade of SNAPSHOT bundle: "
  794                   + installedBundle.getSymbolicName());
  795               return false;
  796           }
  797   
  798           return newVersion.compareTo(installedVersion) <= 0;
  799       }
  800   
  801       /**
  802        * Returns <code>true</code> if the bundle must be assumed to be a fragment
  803        * according to its <code>Fragment-Host</code> header.
  804        */
  805       private static boolean isFragment(Bundle bundle) {
  806           Dictionary<?, ?> headerMap = bundle.getHeaders();
  807           return headerMap.get(Constants.FRAGMENT_HOST) != null;
  808       }
  809   
  810       /**
  811        * Determine if the bundle containing the passed manfiest is a newer
  812        * SNAPSHOT than the already-installed bundle.
  813        *
  814        * @param installedBundle the already-installed bundle
  815        * @param manifest the manifest of the to-be-installed bundle
  816        * @return true if the to-be-installed bundle is newer or if the comparison
  817        *         fails for some reason
  818        */
  819       private boolean isNewerSnapshot(Bundle installedBundle, Manifest manifest) {
  820           String installedDate = (String) installedBundle.getHeaders().get(
  821               BND_LAST_MODIFIED_HEADER);
  822           String toBeInstalledDate = manifest.getMainAttributes().getValue(
  823               BND_LAST_MODIFIED_HEADER);
  824           if (installedDate == null) {
  825               logger.log(Logger.LOG_DEBUG, String.format(
  826                   "Currently installed bundle %s doesn't have a %s header",
  827                   installedBundle.getSymbolicName(), BND_LAST_MODIFIED_HEADER));
  828               return true;
  829           }
  830           if (toBeInstalledDate == null) {
  831               logger.log(Logger.LOG_DEBUG, String.format(
  832                   "Candidate bundle %s doesn't have a %s header",
  833                   installedBundle.getSymbolicName(), BND_LAST_MODIFIED_HEADER));
  834               return true;
  835           }
  836   
  837           long installedTime, toBeInstalledTime = 0;
  838           try {
  839               installedTime = Long.valueOf(installedDate);
  840           } catch (NumberFormatException e) {
  841               logger.log(Logger.LOG_DEBUG, String.format(
  842                   "%s header of currently installed bundle %s isn't parseable.",
  843                   BND_LAST_MODIFIED_HEADER, installedBundle.getSymbolicName()));
  844               return true;
  845           }
  846           try {
  847               toBeInstalledTime = Long.valueOf(toBeInstalledDate);
  848           } catch (NumberFormatException e) {
  849               logger.log(Logger.LOG_DEBUG, String.format(
  850                   "%s header of candidate bundle %s isn't parseable.",
  851                   BND_LAST_MODIFIED_HEADER, installedBundle.getSymbolicName()));
  852               return true;
  853           }
  854   
  855           return toBeInstalledTime > installedTime;
  856   
  857       }
  858   
  859       // ---------- Bundle Installation marker file
  860   
  861       private boolean isAlreadyInstalled(BundleContext context,
  862               File slingStartupDir) {
  863           final File dataFile = context.getDataFile(DATA_FILE);
  864           if (dataFile != null && dataFile.exists()) {
  865   
  866               FileInputStream fis = null;
  867               try {
  868   
  869                   long selfStamp = getSelfTimestamp(slingStartupDir);
  870                   if (selfStamp > 0) {
  871   
  872                       fis = new FileInputStream(dataFile);
  873                       byte[] bytes = new byte[20];
  874                       int len = fis.read(bytes);
  875                       String value = new String(bytes, 0, len);
  876   
  877                       long storedStamp = Long.parseLong(value);
  878   
  879                       return storedStamp >= selfStamp;
  880                   }
  881   
  882               } catch (NumberFormatException nfe) {
  883                   // probably still the old value, fallback to assume not
  884                   // installed
  885   
  886               } catch (IOException ioe) {
  887                   logger.log(Logger.LOG_ERROR,
  888                       "IOException during reading of installed flag.", ioe);
  889   
  890               } finally {
  891                   if (fis != null) {
  892                       try {
  893                           fis.close();
  894                       } catch (IOException ignore) {
  895                       }
  896                   }
  897               }
  898           }
  899   
  900           // fallback assuming not installed yet
  901           return false;
  902       }
  903   
  904       private void markInstalled(BundleContext context, File slingStartupDir) {
  905           final File dataFile = context.getDataFile(DATA_FILE);
  906           try {
  907               final FileOutputStream fos = new FileOutputStream(dataFile);
  908               try {
  909                   fos.write(String.valueOf(getSelfTimestamp(slingStartupDir)).getBytes());
  910               } finally {
  911                   try {
  912                       fos.close();
  913                   } catch (IOException ignore) {
  914                   }
  915               }
  916           } catch (IOException ioe) {
  917               logger.log(Logger.LOG_ERROR,
  918                   "IOException during writing of installed flag.", ioe);
  919           }
  920       }
  921   
  922       /**
  923        * Returns the time stamp of JAR file from which this class has been loaded
  924        * or -1 if the timestamp cannot be resolved.
  925        * <p>
  926        * This method assumes that the ClassLoader of this class is an
  927        * URLClassLoader and that the first URL entry of this class loader is the
  928        * JAR providing this class. This is in fact true as the URLClassLoader has
  929        * been created by the launcher from the launcher JAR file.
  930        *
  931        * @return The last modification time stamp of the launcher JAR file or -1
  932        *         if the class loader of this class is not an URLClassLoader or the
  933        *         class loader has no URL entries. Both situations are not really
  934        *         expected.
  935        * @throws IOException If an error occurrs reading accessing the last
  936        *             modification time stampe.
  937        */
  938       private long getSelfTimestamp(File slingStartupDir) throws IOException {
  939   
  940           // the timestamp of the launcher jar
  941           long selfStamp = -1;
  942           ClassLoader loader = getClass().getClassLoader();
  943           if (loader instanceof URLClassLoader) {
  944               URLClassLoader urlLoader = (URLClassLoader) loader;
  945               URL[] urls = urlLoader.getURLs();
  946               if (urls.length > 0) {
  947                   selfStamp = urls[0].openConnection().getLastModified();
  948               }
  949           }
  950   
  951           // check whether any bundle is younger than the launcher jar
  952           File[] directories = slingStartupDir.listFiles(DIRECTORY_FILTER);
  953           for (File levelDir : directories) {
  954   
  955               // iterate through all files in the startlevel dir
  956               File[] jarFiles = levelDir.listFiles(BUNDLE_FILE_FILTER);
  957               for (File bundleJar : jarFiles) {
  958                   if (bundleJar.lastModified() > selfStamp) {
  959                       selfStamp = bundleJar.lastModified();
  960                   }
  961               }
  962           }
  963   
  964           // return the final stamp (may be -1 if launcher jar cannot be checked
  965           // and there are no bundle jar files)
  966           return selfStamp;
  967       }
  968   
  969       //---------- FileFilter implementations to scan startup folders
  970   
  971       /**
  972        * Simple directory filter
  973        */
  974       private static final FileFilter DIRECTORY_FILTER = new FileFilter() {
  975           public boolean accept(File f) {
  976               return f.isDirectory();
  977           }
  978       };
  979   
  980       /**
  981        * Simple bundle file filter
  982        */
  983       private static final FileFilter BUNDLE_FILE_FILTER = new FileFilter() {
  984           public boolean accept(File f) {
  985               return f.isFile() && isBundle(f.getName());
  986           }
  987       };
  988   
  989       //---------- helper
  990   
  991       private static int getStartLevel(Map<String, String> props) {
  992           // check requested startlevel from the startup properties
  993           final String startLevelS = props.get(Constants.FRAMEWORK_BEGINNING_STARTLEVEL);
  994           if (startLevelS != null) {
  995               try {
  996                   int startLevel = Integer.parseInt(startLevelS);
  997                   if (startLevel >= 1) {
  998                       return startLevel;
  999                   }
 1000               } catch (NumberFormatException nfe) {
 1001                   // don't care much
 1002               }
 1003           }
 1004   
 1005           // fall back to default startlevel
 1006           return 1;
 1007       }
 1008   
 1009       /**
 1010        * Simple check to see if a string is blank since
 1011        * StringUtils is not available here, maybe fix this later
 1012        * @param str the string to check
 1013        * @return true if the string is null or empty OR false otherwise
 1014        */
 1015       static boolean isBlank(String str) {
 1016           return str == null || str.length() == 0 || str.trim().length() == 0;
 1017       }
 1018   
 1019       /**
 1020        * @param path any path (cannot be blank)
 1021        * @return the filename from the end of the path
 1022        * @throws IllegalArgumentException if there is no filename available
 1023        */
 1024       static String extractFileName(String path) {
 1025           if (isBlank(path)) {
 1026               throw new IllegalArgumentException("Invalid blank path specified, cannot extract filename: " + path);
 1027           }
 1028   
 1029           // ensure forward slashes in the path
 1030           path = path.replace(File.separatorChar, '/');
 1031   
 1032           String name = "";
 1033           int slashPos = path.lastIndexOf('/');
 1034           if (slashPos == -1) {
 1035               // this is only a filename (no directory path included)
 1036               name = path;
 1037           } else if (path.length() > slashPos+1) {
 1038               // split off the ending of the path
 1039               name = path.substring(slashPos+1);
 1040           }
 1041           if (isBlank(name)) {
 1042               throw new IllegalArgumentException("Invalid path, no filename found: " + path);
 1043           }
 1044           return name;
 1045       }
 1046   
 1047   }

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