/* * Copyright (c) 2004, KNOPFLERFISH project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * - Neither the name of the KNOPFLERFISH project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.knopflerfish.bundle.dirdeployer; import java.util.*; import java.io.*; import java.net.*; import org.osgi.framework.*; import org.osgi.util.tracker.ServiceTracker; import org.osgi.service.startlevel.*; import org.osgi.service.log.LogService; import org.knopflerfish.service.dirdeployer.*; /** * Implementatation of DirDeployerService which * scans a set of directories regularely on a thread. * */ class DirDeployerImpl implements DirDeployerService { // File -> DeployedFile Hashtable deployedFiles = new Hashtable(); Thread runner = null; // scan thread boolean bRun = false; // flag for stopping scan thread ServiceTracker logTracker; Config config; public DirDeployerImpl() { // create and register the configuration class config = new Config(); logTracker = new ServiceTracker(Activator.bc, LogService.class.getName(), null); logTracker.open(); } /** * Start the scanner thread if not already started. Also register * the config object. */ void start() { config.register(); if(runner != null) { return; } runner = new Thread("Directory deployer") { public void run() { log("started scan"); while(bRun) { try { synchronized(config) { long now = System.currentTimeMillis(); doScan(); long time = System.currentTimeMillis() - now; // make sure we don't spend all our time in scanning dirs if(time > config.interval) { config.interval = Math.max(100, time * 2); log("increased interval to " + config.interval); } } Thread.sleep(Math.max(100, config.interval)); } catch (Exception e) { log("scan failed", e); } } log("stopped scan"); if(config.bUninstallOnStop) { uninstallAll(); } } }; bRun = true; runner.start(); } /** * Stop the scanner thread if not already stopped. Also unregister the * config object. */ void stop() { config.unregister(); if(runner == null) { return; } try { bRun = false; runner.join(config.interval * 2); } catch (Exception ignored) { } runner = null; } /** * Scan for new, updated and lost files. */ void doScan() { synchronized(config) { for(int i = 0; i < config.dirs.length; i++) { scanForNewFiles(config.dirs[i]); } } scanForLostFiles(); startDelayed(); } /** * Scan a directory for new/updated files. * *

* Check if any new files have appeared or * if any already deployed files has been replaced with newer * files. *

* *

* New files are installed (and marked for delayed start if they have an * activator). Files newer than a previously deployed bundle are updated. *

* *

* Files that have the same location as an already installed * bundle is not installed again, instead, the installed bundle is * re-used in the created DeployedFile instance. *

*/ void scanForNewFiles(File dir) { synchronized(deployedFiles) { // log("scanForNewFiles " + dir); if(dir != null && dir.exists() && dir.isDirectory()) { String[] files = dir.list(); for(int i = 0; i < files.length; i++) { try { File f = new File(dir, files[i]); if(isBundleFile(f)) { DeployedFile dp = (DeployedFile)deployedFiles.get(f); if(dp != null) { if(dp.needUpdate()) { dp.update(); } } else { dp = new DeployedFile(f); deployedFiles.put(f, dp); dp.install(); // will install or re-use as appropiate // Mark to be started later on // this is to allow better package resolving if(null != dp.bundle.getHeaders().get("Bundle-Activator")) { dp.bDelayedStart = true; } } } } catch (Exception e) { log("scan failed", e); } } } } } /** * Check if any files has been removed from any of the scanned * dirs, If so, uninstall * them and remove from deployed map. */ void scanForLostFiles() { synchronized(deployedFiles) { Vector removed = new Vector(); // check, uninstall and copy to removed vector as // necessary for(Enumeration e = deployedFiles.keys(); e.hasMoreElements(); ) { File f = (File)e.nextElement(); if(!f.exists() || !isInScannedDirs(f)) { try { DeployedFile dp = (DeployedFile)deployedFiles.get(f); dp.uninstall(); removed.addElement(f); } catch (Exception ex) { ex.printStackTrace(); } } } // ...then remove from map for(int i = 0; i < removed.size(); i++) { File f = (File)removed.elementAt(i); deployedFiles.remove(f); } } } /** * Start bundles marked for delayed start. */ void startDelayed() { for(Enumeration e = deployedFiles.keys(); e.hasMoreElements(); ) { File f = (File)e.nextElement(); try { DeployedFile dp = (DeployedFile)deployedFiles.get(f); if(dp.bDelayedStart) { dp.start(); } } catch (Exception ex) { ex.printStackTrace(); } } } /** * Uninstall all deployed bundles and clear deploy map */ void uninstallAll() { synchronized(deployedFiles) { for(Enumeration e = deployedFiles.keys(); e.hasMoreElements(); ) { File f = (File)e.nextElement(); try { DeployedFile dp = (DeployedFile)deployedFiles.get(f); dp.uninstall(); } catch (Exception ex) { ex.printStackTrace(); } } deployedFiles.clear(); } } /** * Same as log(msg, null) */ void log(String s) { log(s, null); } /** * Log msg to log service if available and to stdout if * not available. */ void log(String msg, Throwable t) { int level = t == null ? LogService.LOG_INFO : LogService.LOG_WARNING; LogService log = (LogService)logTracker.getService(); if(log == null) { System.out.println("[dirdeployer " + level + "] " + msg); if(t != null) { t.printStackTrace(); } } else { log.log(level, msg, t); } } /** * Check if a file is in one of the scanned dirs */ boolean isInScannedDirs(File f) { synchronized(config) { for(int i = 0; i < config.dirs.length; i++) { if(config.dirs[i].equals(new File(f.getParent()))) { return true; } } return false; } } /** * Check if a file seems to be a bundle jar file. */ static boolean isBundleFile(File f) { return f.toString().toLowerCase().endsWith(".jar"); } /** * Utility class to store info about a deployed bundle. */ class DeployedFile { String location; File file; Bundle bundle; long lastUpdate = -1; boolean bDelayedStart = false; /** * Create a deployedfile instance from a specified file. * * @throws RuntimeException if the specified does not exists */ public DeployedFile(File f) { if(!f.exists()) { throw new RuntimeException("No file " + f); } this.file = f; // location URL to be used for for installing bundle location = "file:" + file.getAbsolutePath(); } /** * Install into framework. * *

* If the location for this DeployFile already is installed * in the framework, re-use the installed Bundle instance, * otherwise install using BundleContext.installBundle */ public void install() throws BundleException { ServiceReference startLevelSR = null; try { Bundle[] bl = Activator.bc.getBundles(); for(int i = 0; i < bl.length; i++) { if(location.equals(bl[i].getLocation())) { bundle = bl[i]; log("already installed " + this); } } if(bundle == null) { bundle = Activator.bc.installBundle(location); // Set bundle start level if possible try { startLevelSR = Activator.bc.getServiceReference(StartLevel.class.getName()); StartLevel sl = (StartLevel)Activator.bc.getService(startLevelSR); sl.setBundleStartLevel(bundle, config.startLevel); } catch (Exception e) { log("Failed to set start level for " + this, e); } log("installed " + this); } lastUpdate = System.currentTimeMillis(); } finally { if(startLevelSR != null) { Activator.bc.ungetService(startLevelSR); } } } public void start() throws BundleException { log("start " + this); bDelayedStart = false; // delayed start is only valid once bundle.start(); } public void update() throws BundleException { log("update " + this); bundle.update(); lastUpdate = System.currentTimeMillis(); } public void uninstall() throws BundleException { log("uninstall " + this); bundle.uninstall(); bundle = null; lastUpdate = -1; } /** * A deployed bundle should be updated if the bundle file * is newer than tha latest update time. */ public boolean needUpdate() { return file.lastModified() > lastUpdate; } public String toString() { return "DeployedFile[" + "location=" + location + ", bundle=" + bundle + "]"; } } }