| /* |
| * 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.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.Dictionary; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.jar.JarFile; |
| import java.util.jar.Manifest; |
| |
| import org.apache.felix.framework.Logger; |
| import org.apache.sling.launchpad.api.LaunchpadContentProvider; |
| import org.apache.sling.launchpad.api.StartupMode; |
| import org.apache.sling.launchpad.base.impl.bootstrapcommands.BootstrapCommandFile; |
| import org.apache.sling.launchpad.base.shared.SharedConstants; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.BundleException; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.Version; |
| import org.osgi.framework.startlevel.BundleStartLevel; |
| |
| /** |
| * The <code>BootstrapInstaller</code> class is installed into the OSGi |
| * framework as an activator to be called when the framework is starting up. |
| * Upon startup all bundles from the {@link #PATH_CORE_BUNDLES} and the |
| * {@link #PATH_BUNDLES} location are checked whether they are already installed |
| * or not. If they are not installed, they are installed, their start level set |
| * to 1 and started. Any bundle already installed is not installed again and |
| * will also not be started here. |
| */ |
| class BootstrapInstaller { |
| |
| /** |
| * The Bundle location scheme (protocol) used for bundles installed by this |
| * activator (value is "slinginstall:"). The path part of the Bundle |
| * location of Bundles installed by this class is the name (without the |
| * path) of the resource from which the Bundle was installed. |
| */ |
| private static final String SCHEME = "slinginstall:"; |
| |
| /** |
| * The root location in which the bundles are looked up for installation |
| * (value is "resources/"). |
| */ |
| private static final String PATH_RESOURCES = "resources/"; |
| |
| /** |
| * The location of the core Bundles (value is "resources/corebundles"). |
| * These bundles are installed at startlevel |
| * {@link #STARTLEVEL_CORE_BUNDLES}. |
| * <p> |
| * This location is deprecated, instead these core bundles should be located |
| * in <code>resources/bundles/1</code>. |
| */ |
| static final String PATH_CORE_BUNDLES = PATH_RESOURCES + "corebundles"; |
| |
| /** |
| * The location the additional Bundles (value is "resources/bundles"). These |
| * Bundles are installed after the {@link #PATH_CORE_BUNDLES core Bundles}. |
| */ |
| static final String PATH_BUNDLES = PATH_RESOURCES + "bundles"; |
| |
| /** |
| * The header which contains the bundle's last modified date. |
| */ |
| static final String BND_LAST_MODIFIED_HEADER = "Bnd-LastModified"; |
| |
| /** |
| * The start level to be assigned to bundles found in the (old style) |
| * {@link #PATH_CORE_BUNDLES resources/corebundles} location (value is 1). |
| */ |
| private static final int STARTLEVEL_CORE_BUNDLES = 1; |
| |
| /** |
| * The start level to be assigned to bundles found in the (old style) |
| * {@link #PATH_BUNDLES resources/bundles} location (value is 0). |
| */ |
| private static final int STARTLEVEL_BUNDLES = 0; |
| |
| /** |
| * The marker start level indicating the location of the bundle cannot be |
| * resolved to a valid start level (value is -1). |
| */ |
| private static final int STARTLEVEL_NONE = -1; |
| |
| /** |
| * The name of the bootstrap commands file |
| */ |
| public static final String BOOTSTRAP_CMD_FILENAME = "sling_bootstrap.txt"; |
| |
| /** |
| * The {@link Logger} use for logging messages during installation and |
| * startup. |
| */ |
| private final Logger logger; |
| |
| /** |
| * The {@link LaunchpadContentProvider} used to access the Bundle files to |
| * install. |
| */ |
| private final LaunchpadContentProvider resourceProvider; |
| |
| /** The bundle context. */ |
| private final BundleContext bundleContext; |
| |
| /** The startup mode. */ |
| private final StartupMode startupMode; |
| |
| BootstrapInstaller(final BundleContext bundleContext, |
| final Logger logger, |
| final LaunchpadContentProvider resourceProvider, |
| final StartupMode startupMode) { |
| this.startupMode = startupMode; |
| this.logger = logger; |
| this.resourceProvider = resourceProvider; |
| this.bundleContext = bundleContext; |
| } |
| |
| /** |
| * https://issues.apache.org/jira/browse/SLING-922 |
| * Handles the initial detection and installation of bundles into |
| * the Felix OSGi running in Sling |
| * |
| * Process: |
| * 1) Copy all bundles from enclosed resources (jar/war) to |
| * ${sling.home}/startup. This gives something like |
| * ${sling.home}/startup/0, /1, /10, /15, ... |
| * Existing files are only replaced if the files |
| * enclosed in the Sling launchpad jar/war file are newer. |
| * 2) Scan ${sling.home}/startup for bundles to install |
| * in the same way as today the enclosed resources |
| * are scanned directly. |
| * So you could place your bundles in that structure and get them installed |
| * at the requested start level (0 being "default bundle start level"). |
| */ |
| boolean install() throws IOException { |
| |
| String launchpadHome = bundleContext.getProperty(SharedConstants.SLING_LAUNCHPAD); |
| if (launchpadHome == null) { |
| launchpadHome = bundleContext.getProperty(SharedConstants.SLING_HOME); |
| } |
| final File slingStartupDir = getSlingStartupDir(launchpadHome); |
| |
| // execute bootstrap commands, if needed |
| final BootstrapCommandFile cmd = new BootstrapCommandFile(logger, |
| new File(launchpadHome, BOOTSTRAP_CMD_FILENAME)); |
| boolean requireRestart = cmd.execute(bundleContext); |
| |
| boolean shouldInstall = false; |
| |
| // see if the loading of bundles from the package is forced |
| final String fpblString = bundleContext.getProperty(SharedConstants.FORCE_PACKAGE_BUNDLE_LOADING); |
| if (Boolean.valueOf(fpblString)) { |
| shouldInstall = true; |
| } else { |
| shouldInstall = this.startupMode != StartupMode.RESTART; |
| } |
| |
| if (shouldInstall) { |
| // only run the war/jar copies when this war/jar is new/changed |
| |
| // see if the loading of bundles from the package is disabled |
| String dpblString = bundleContext.getProperty(SharedConstants.DISABLE_PACKAGE_BUNDLE_LOADING); |
| Boolean disablePackageBundleLoading = Boolean.valueOf(dpblString); |
| |
| if (disablePackageBundleLoading) { |
| 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"); |
| } else { |
| // get the bundles out of the jar/war and copy them to the startup location |
| Iterator<String> resources = resourceProvider.getChildren(PATH_BUNDLES); |
| while (resources.hasNext()) { |
| String path = resources.next(); |
| // only consider folders |
| if (path.endsWith("/")) { |
| |
| // cut off trailing slash |
| path = path.substring(0, path.length() - 1); |
| |
| // calculate the startlevel of bundles contained |
| int startLevel = getStartLevel(path); |
| if (startLevel != STARTLEVEL_NONE) { |
| copyBundles(slingStartupDir, path, startLevel); |
| } |
| } |
| } |
| |
| // copy old-style core bundles |
| copyBundles(slingStartupDir, PATH_CORE_BUNDLES, STARTLEVEL_CORE_BUNDLES); |
| |
| // copy old-style bundles |
| copyBundles(slingStartupDir, PATH_BUNDLES, STARTLEVEL_BUNDLES); |
| |
| // done with copying at this point |
| } |
| |
| // get the set of all existing (installed) bundles by symbolic name |
| Bundle[] bundles = bundleContext.getBundles(); |
| Map<String, Bundle> bySymbolicName = new HashMap<String, Bundle>(); |
| for (int i = 0; i < bundles.length; i++) { |
| bySymbolicName.put(bundles[i].getSymbolicName(), bundles[i]); |
| } |
| |
| // holds the bundles we install during this processing |
| List<Bundle> installed = new LinkedList<Bundle>(); |
| |
| // get all bundles from the startup location and install them |
| requireRestart |= installBundles(slingStartupDir, bySymbolicName, installed); |
| |
| // start all the newly installed bundles (existing bundles are not started if they are stopped) |
| startBundles(installed); |
| } |
| |
| // due to the upgrade of a framework extension bundle, the framework |
| // has to be restarted. For this reason, set the target start level |
| // to a negative value. |
| if (requireRestart) { |
| logger.log( |
| Logger.LOG_INFO, |
| "Framework extension(s) have been updated, restarting framework after startup has completed"); |
| } |
| |
| return requireRestart; |
| } |
| |
| //---------- Startup folder maintenance |
| |
| /** |
| * Get the sling startup directory (or create it) in the sling home if possible |
| * @param slingHome the path to the sling home |
| * @return the sling startup directory |
| * @throws IllegalStateException if the sling home or startup directories cannot be created/accessed |
| */ |
| private File getSlingStartupDir(String slingHome) { |
| final File slingHomeDir = new File(slingHome); |
| final File slingHomeStartupDir = getOrCreateDirectory(slingHomeDir, DirectoryUtil.PATH_STARTUP); |
| return slingHomeStartupDir; |
| } |
| |
| /** |
| * Get or create a sub-directory from an existing parent |
| * @param parentDir the parent directory |
| * @param subDirName the name of the sub-directory |
| * @return the sub-directory |
| * @throws IllegalStateException if directory cannot be created/accessed |
| */ |
| private File getOrCreateDirectory(File parentDir, String subDirName) { |
| File slingHomeStartupDir = new File(parentDir, subDirName).getAbsoluteFile(); |
| if ( slingHomeStartupDir.exists() ) { |
| if (! slingHomeStartupDir.isDirectory() |
| || ! parentDir.canRead() |
| || ! parentDir.canWrite() ) { |
| throw new IllegalStateException("Fatal error in bootstrap: Cannot find accessible existing " |
| +SharedConstants.SLING_HOME+DirectoryUtil.PATH_STARTUP+" directory: " + slingHomeStartupDir); |
| } |
| } else if (! slingHomeStartupDir.mkdirs() ) { |
| throw new IllegalStateException("Sling Home " + slingHomeStartupDir + " cannot be created as a directory"); |
| } |
| return slingHomeStartupDir; |
| } |
| |
| /** |
| * Copies the bundles from the given parent location in the jar/war |
| * to the startup directory in the sling.home based on the startlevel |
| * e.g. {sling.home}/startup/{startLevel} |
| */ |
| private void copyBundles(File slingStartupDir, String parent, int startLevel) { |
| |
| // set default start level |
| if (startLevel < 0) { |
| startLevel = 0; |
| } |
| // this will be set and created on demand |
| File startUpLevelDir = null; |
| |
| Iterator<String> res = resourceProvider.getChildren(parent); |
| while (res.hasNext()) { |
| // path to the next resource |
| String path = res.next(); |
| |
| if (DirectoryUtil.isBundle(path)) { |
| // try to access the bundle file, ignore if not possible |
| InputStream ins = resourceProvider.getResourceAsStream(path); |
| if (ins == null) { |
| continue; |
| } |
| |
| try { |
| // ensure we have a directory for the startlevel only when |
| // needed |
| if (startUpLevelDir == null) { |
| startUpLevelDir = getOrCreateDirectory(slingStartupDir, |
| String.valueOf(startLevel)); |
| } |
| |
| // copy over the bundle based on the startlevel |
| String bundleFileName = extractFileName(path); |
| File bundleFile = new File(startUpLevelDir, bundleFileName); |
| try { |
| copyStreamToFile(ins, bundleFile); |
| } catch (IOException e) { |
| // should this fail here or just log a warning? |
| throw new RuntimeException("Failure copying file from " |
| + path + " to startup dir (" + startUpLevelDir |
| + ") and name (" + bundleFileName + "): " + e, e); |
| } |
| } finally { |
| try { |
| ins.close(); |
| } catch (IOException ignore) { |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Copies a stream from the resource (jar/war) to a file |
| * @param fromStream |
| * @param toFile |
| */ |
| static void copyStreamToFile(InputStream fromStream, File toFile) throws IOException { |
| if (fromStream == null || toFile == null) { |
| throw new IllegalArgumentException("fromStream and toFile must not be null"); |
| } |
| if (! toFile.exists()) { |
| toFile.createNewFile(); |
| } |
| // overwrite |
| OutputStream out = new FileOutputStream(toFile); |
| try { |
| byte[] buf = new byte[1024]; |
| int len; |
| while ((len = fromStream.read(buf)) > 0) { |
| out.write(buf, 0, len); |
| } |
| } finally { |
| out.close(); |
| } |
| } |
| |
| /** |
| * Install the Bundles from files found in startup directory under the |
| * level directories, this will only install bundles which are new or updated |
| * and will skip over them otherwise |
| * |
| * @param context The <code>BundleContext</code> used to install the new Bundles. |
| * @param currentBundles The currently installed Bundles indexed by their |
| * Bundle location. |
| * @param parent The path to the location in which to look for bundle files to |
| * install. Only resources whose name ends with one of the known bundle extensions are |
| * considered for installation. |
| * @param installed The list of Bundles installed by this method. Each |
| * Bundle successfully installed is added to this list. |
| * |
| * @return <code>true</code> if a system bundle fragment was updated which |
| * requires the framework to restart. |
| */ |
| private boolean installBundles(final File slingStartupDir, |
| final Map<String, Bundle> currentBundles, |
| final List<Bundle> installed) { |
| |
| boolean requireRestart = false; |
| File[] directories = slingStartupDir.listFiles(DirectoryUtil.DIRECTORY_FILTER); |
| for (File levelDir : directories) { |
| // get startlevel from dir name |
| String dirName = levelDir.getName(); |
| int startLevel; |
| try { |
| startLevel = Integer.decode(dirName); |
| } catch (NumberFormatException e) { |
| startLevel = 0; |
| } |
| |
| // iterate through all files in the startlevel dir |
| File[] bundleFiles = levelDir.listFiles(DirectoryUtil.BUNDLE_FILE_FILTER); |
| for (File bundleFile : bundleFiles) { |
| requireRestart |= installBundle(bundleFile, startLevel, |
| currentBundles, installed); |
| } |
| } |
| |
| return requireRestart; |
| } |
| |
| /** |
| * @param bundleJar the jar file for the bundle to install |
| * @param startLevel the start level to use for this bundle |
| * @param context The <code>BundleContext</code> used to install the new Bundles. |
| * @param currentBundles The currently installed Bundles indexed by their |
| * Bundle location. |
| * @param installed The list of Bundles installed by this method. Each |
| * Bundle successfully installed is added to this list. |
| * @param startLevelService the service which sets the start level |
| * |
| * @return <code>true</code> if a system bundle fragment was updated which |
| * requires the framework to restart. |
| */ |
| private boolean installBundle(final File bundleJar, |
| final int startLevel, |
| final Map<String, Bundle> currentBundles, |
| final List<Bundle> installed) { |
| // get the manifest for the bundle information |
| Manifest manifest = getManifest(bundleJar); |
| if (manifest == null) { |
| logger.log(Logger.LOG_ERROR, "Ignoring " + bundleJar |
| + ": Cannot read manifest"); |
| return false; // SHORT CIRCUIT |
| } |
| |
| // ensure a symbolic name in the jar file |
| String symbolicName = getBundleSymbolicName(manifest); |
| if (symbolicName == null) { |
| logger.log(Logger.LOG_ERROR, "Ignoring " + bundleJar |
| + ": Missing " + Constants.BUNDLE_SYMBOLICNAME |
| + " in manifest"); |
| return false; // SHORT CIRCUIT |
| } |
| |
| // check for an installed Bundle with the symbolic name |
| Bundle installedBundle = currentBundles.get(symbolicName); |
| if (ignore(installedBundle, manifest)) { |
| logger.log(Logger.LOG_INFO, "Ignoring " + bundleJar |
| + ": More recent version already installed"); |
| return false; // SHORT CIRCUIT |
| } |
| |
| // try to access the JAR file, ignore if not possible |
| InputStream ins; |
| try { |
| ins = new FileInputStream(bundleJar); |
| } catch (FileNotFoundException e) { |
| return false; // SHORT CIRCUIT |
| } |
| |
| final boolean requireRestart; |
| if (installedBundle != null) { |
| |
| // if the installed bundle is an extension fragment we have to |
| // restart the framework after completing the installation |
| // or upgrade of all new bundles |
| requireRestart = isSystemBundleFragment(installedBundle); |
| |
| try { |
| installedBundle.update(ins); |
| logger.log(Logger.LOG_INFO, "Bundle " |
| + installedBundle.getSymbolicName() |
| + " updated from " + bundleJar); |
| |
| // optionally set the start level |
| if (startLevel > 0) { |
| installedBundle.adapt(BundleStartLevel.class).setStartLevel(startLevel); |
| } |
| } catch (BundleException be) { |
| logger.log(Logger.LOG_ERROR, "Bundle update from " |
| + bundleJar + " failed", be); |
| } |
| |
| } else { |
| |
| // restart is not required for any bundle installation at this |
| // stage |
| requireRestart = false; |
| |
| // install the JAR file as a bundle |
| String path = bundleJar.getPath(); |
| String location = SCHEME |
| + path.substring(path.lastIndexOf('/') + 1); |
| try { |
| Bundle theBundle = bundleContext.installBundle(location, ins); |
| logger.log(Logger.LOG_INFO, "Bundle " |
| + theBundle.getSymbolicName() + " installed from " |
| + location); |
| |
| // finally add the bundle to the list for later start |
| installed.add(theBundle); |
| |
| // optionally set the start level |
| if (startLevel > 0) { |
| theBundle.adapt(BundleStartLevel.class).setStartLevel(startLevel); |
| } |
| |
| } catch (BundleException be) { |
| logger.log(Logger.LOG_ERROR, |
| "Bundle installation from " + location + " failed", be); |
| } |
| } |
| |
| return requireRestart; |
| } |
| |
| /** |
| * Starts the Bundles in the <code>bundles</code> list. If the framework |
| * provides an active <code>StartLevel</code> service, the start levels of |
| * the Bundles is first set to <em>1</em>. |
| */ |
| private void startBundles(final List<Bundle> bundles) { |
| |
| // start all bundles |
| for (final Bundle bundle : bundles) { |
| try { |
| if (!isFragment(bundle)) { |
| bundle.start(); |
| } |
| } catch (final BundleException be) { |
| logger.log(Logger.LOG_ERROR, "Bundle " |
| + bundle.getSymbolicName() + " could not be started", be); |
| } |
| } |
| |
| } |
| |
| private int getStartLevel(final String path) { |
| final String name = path.substring(path.lastIndexOf('/') + 1); |
| try { |
| int level = Integer.parseInt(name); |
| if (level >= 0) { |
| return level; |
| } |
| |
| logger.log(Logger.LOG_ERROR, "Illegal Runlevel for " + path |
| + ", ignoring"); |
| } catch (final NumberFormatException nfe) { |
| logger.log(Logger.LOG_INFO, "Folder " + path |
| + " does not denote start level, ignoring"); |
| } |
| |
| // no valid start level, ignore this location |
| return STARTLEVEL_NONE; |
| } |
| |
| private boolean isSystemBundleFragment(final Bundle installedBundle) { |
| final String fragmentHeader = installedBundle.getHeaders().get( |
| Constants.FRAGMENT_HOST); |
| return fragmentHeader != null |
| && fragmentHeader.indexOf(Constants.EXTENSION_DIRECTIVE) > 0; |
| } |
| |
| // ---------- Bundle JAR file information |
| |
| /** |
| * Returns the Manifest from the JAR file in the given resource provided by |
| * the resource provider or <code>null</code> if the resource does not |
| * exists or is not a JAR file or has no Manifest. |
| * |
| * @param jarPath The path to the JAR file provided by the resource provider |
| * of this instance. |
| */ |
| private Manifest getManifest(final File jar) { |
| JarFile jarFile = null; |
| try { |
| jarFile = new JarFile(jar, false); |
| return jarFile.getManifest(); |
| } catch (IOException e) { |
| logger.log(Logger.LOG_WARNING, |
| "Could not get inputstream from file (" + jar + "):" + e); |
| } finally { |
| if (jarFile != null) { |
| try { |
| jarFile.close(); |
| } catch (IOException ignore) { |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the <i>Bundle-SymbolicName</i> header from the given manifest or |
| * <code>null</code> if no such header exists. |
| * <p> |
| * Note that bundles are not allowed to have no symbolic name any more. |
| * Therefore a bundle without a symbolic name header should not be |
| * installed. |
| * |
| * @param manifest The Manifest from which to extract the header. |
| */ |
| static String getBundleSymbolicName(final Manifest manifest) { |
| return manifest.getMainAttributes().getValue( |
| Constants.BUNDLE_SYMBOLICNAME); |
| } |
| |
| /** |
| * Checks whether the installed bundle is at the same version (or more |
| * recent) than the bundle described by the given manifest. |
| * |
| * @param installedBundle The bundle currently installed in the framework |
| * @param manifest The Manifest describing the bundle version potentially |
| * updating the installed bundle |
| * @return <code>true</code> if the manifest does not describe a bundle with |
| * a higher version number. |
| */ |
| private boolean ignore(final Bundle installedBundle, final Manifest manifest) { |
| |
| // the bundle is not installed yet, so we have to install it |
| if (installedBundle == null) { |
| return false; |
| } |
| |
| String versionProp = manifest.getMainAttributes().getValue( |
| Constants.BUNDLE_VERSION); |
| Version newVersion = Version.parseVersion(versionProp); |
| |
| String installedVersionProp = installedBundle.getHeaders().get( |
| Constants.BUNDLE_VERSION); |
| Version installedVersion = Version.parseVersion(installedVersionProp); |
| |
| // if the new version and the current version are the same, reinstall if |
| // the version is a snapshot |
| if (newVersion.equals(installedVersion) |
| && installedVersionProp.endsWith("SNAPSHOT") |
| && isNewerSnapshot(installedBundle, manifest)) { |
| logger.log(Logger.LOG_INFO, "Forcing upgrade of SNAPSHOT bundle: " |
| + installedBundle.getSymbolicName()); |
| return false; |
| } |
| |
| return newVersion.compareTo(installedVersion) <= 0; |
| } |
| |
| /** |
| * 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(final Bundle bundle) { |
| Dictionary<?, ?> headerMap = bundle.getHeaders(); |
| return headerMap.get(Constants.FRAGMENT_HOST) != null; |
| } |
| |
| /** |
| * Determine if the bundle containing the passed manfiest is a newer |
| * SNAPSHOT than the already-installed bundle. |
| * |
| * @param installedBundle the already-installed bundle |
| * @param manifest the manifest of the to-be-installed bundle |
| * @return true if the to-be-installed bundle is newer or if the comparison |
| * fails for some reason |
| */ |
| private boolean isNewerSnapshot(final Bundle installedBundle, final Manifest manifest) { |
| String installedDate = installedBundle.getHeaders().get( |
| BND_LAST_MODIFIED_HEADER); |
| String toBeInstalledDate = manifest.getMainAttributes().getValue( |
| BND_LAST_MODIFIED_HEADER); |
| if (installedDate == null) { |
| logger.log(Logger.LOG_DEBUG, String.format( |
| "Currently installed bundle %s doesn't have a %s header", |
| installedBundle.getSymbolicName(), BND_LAST_MODIFIED_HEADER)); |
| return true; |
| } |
| if (toBeInstalledDate == null) { |
| logger.log(Logger.LOG_DEBUG, String.format( |
| "Candidate bundle %s doesn't have a %s header", |
| installedBundle.getSymbolicName(), BND_LAST_MODIFIED_HEADER)); |
| return true; |
| } |
| |
| long installedTime, toBeInstalledTime = 0; |
| try { |
| installedTime = Long.valueOf(installedDate); |
| } catch (NumberFormatException e) { |
| logger.log(Logger.LOG_DEBUG, String.format( |
| "%s header of currently installed bundle %s isn't parseable.", |
| BND_LAST_MODIFIED_HEADER, installedBundle.getSymbolicName())); |
| return true; |
| } |
| try { |
| toBeInstalledTime = Long.valueOf(toBeInstalledDate); |
| } catch (NumberFormatException e) { |
| logger.log(Logger.LOG_DEBUG, String.format( |
| "%s header of candidate bundle %s isn't parseable.", |
| BND_LAST_MODIFIED_HEADER, installedBundle.getSymbolicName())); |
| return true; |
| } |
| |
| return toBeInstalledTime > installedTime; |
| |
| } |
| |
| //---------- helper |
| /** |
| * Simple check to see if a string is blank since |
| * StringUtils is not available here, maybe fix this later |
| * @param str the string to check |
| * @return true if the string is null or empty OR false otherwise |
| */ |
| static boolean isBlank(final String str) { |
| return str == null || str.length() == 0 || str.trim().length() == 0; |
| } |
| |
| /** |
| * @param path any path (cannot be blank) |
| * @return the filename from the end of the path |
| * @throws IllegalArgumentException if there is no filename available |
| */ |
| static String extractFileName(String path) { |
| if (isBlank(path)) { |
| throw new IllegalArgumentException("Invalid blank path specified, cannot extract filename: " + path); |
| } |
| |
| // ensure forward slashes in the path |
| path = path.replace(File.separatorChar, '/'); |
| |
| String name = ""; |
| int slashPos = path.lastIndexOf('/'); |
| if (slashPos == -1) { |
| // this is only a filename (no directory path included) |
| name = path; |
| } else if (path.length() > slashPos+1) { |
| // split off the ending of the path |
| name = path.substring(slashPos+1); |
| } |
| if (isBlank(name)) { |
| throw new IllegalArgumentException("Invalid path, no filename found: " + path); |
| } |
| return name; |
| } |
| |
| } |