You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by gn...@apache.org on 2009/08/31 08:40:44 UTC
svn commit: r809470 [1/2] - in /felix/trunk/fileinstall/src:
main/java/org/apache/felix/fileinstall/
main/java/org/apache/felix/fileinstall/listener/
main/java/org/apache/felix/fileinstall/util/
main/resources/OSGI-INF/metatype/ test/java/org/apache/fe...
Author: gnodet
Date: Mon Aug 31 06:40:43 2009
New Revision: 809470
URL: http://svn.apache.org/viewvc?rev=809470&view=rev
Log:
FELIX-922, FELIX-1483 and FELIX-1377: support for new artifact types, support for exploded artifacts, wait until copy is finished before processing an artifact
Added:
felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/Artifact.java
felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/BundleTransformer.java
felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/ConfigInstaller.java
felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/Scanner.java
felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/
felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactInstaller.java
felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactListener.java
felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactTransformer.java
felix/trunk/fileinstall/src/test/java/org/apache/felix/fileinstall/BundleTransformerTest.java
felix/trunk/fileinstall/src/test/java/org/apache/felix/fileinstall/ConfigInstallerTest.java
- copied, changed from r809199, felix/trunk/fileinstall/src/test/java/org/apache/felix/fileinstall/DirectoryWatcherTest.java
Modified:
felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/DirectoryWatcher.java
felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/FileInstall.java
felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/util/Util.java
felix/trunk/fileinstall/src/main/resources/OSGI-INF/metatype/metatype.xml
felix/trunk/fileinstall/src/test/java/org/apache/felix/fileinstall/DirectoryWatcherTest.java
felix/trunk/fileinstall/src/test/java/org/apache/felix/fileinstall/util/UtilTest.java
Added: felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/Artifact.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/Artifact.java?rev=809470&view=auto
==============================================================================
--- felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/Artifact.java (added)
+++ felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/Artifact.java Mon Aug 31 06:40:43 2009
@@ -0,0 +1,84 @@
+/*
+ * 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.felix.fileinstall;
+
+import java.io.File;
+
+import org.apache.felix.fileinstall.listener.ArtifactListener;
+
+/**
+ * An artifact that has been dropped into one watched directory.
+ */
+public class Artifact {
+
+ private File path;
+ private File jaredDirectory;
+ private long lastModified = -1;
+ private ArtifactListener listener;
+ private File transformed;
+ private long bundleId = -1;
+
+ public File getPath() {
+ return path;
+ }
+
+ public void setPath(File path) {
+ this.path = path;
+ }
+
+ public File getJaredDirectory() {
+ return jaredDirectory;
+ }
+
+ public void setJaredDirectory(File jaredDirectory) {
+ this.jaredDirectory = jaredDirectory;
+ }
+
+ public long getLastModified() {
+ return lastModified;
+ }
+
+ public void setLastModified(long lastModified) {
+ this.lastModified = lastModified;
+ }
+
+ public ArtifactListener getListener() {
+ return listener;
+ }
+
+ public void setListener(ArtifactListener listener) {
+ this.listener = listener;
+ }
+
+ public File getTransformed() {
+ return transformed;
+ }
+
+ public void setTransformed(File transformed) {
+ this.transformed = transformed;
+ }
+
+ public long getBundleId() {
+ return bundleId;
+ }
+
+ public void setBundleId(long bundleId) {
+ this.bundleId = bundleId;
+ }
+}
Added: felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/BundleTransformer.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/BundleTransformer.java?rev=809470&view=auto
==============================================================================
--- felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/BundleTransformer.java (added)
+++ felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/BundleTransformer.java Mon Aug 31 06:40:43 2009
@@ -0,0 +1,82 @@
+/*
+ * 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.felix.fileinstall;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.jar.Attributes;
+
+import org.apache.felix.fileinstall.listener.ArtifactTransformer;
+
+/**
+ * ArtifactTransformer for plain bundles.
+ */
+public class BundleTransformer implements ArtifactTransformer
+{
+ public boolean canHandle(File artifact)
+ {
+ JarFile jar = null;
+ try
+ {
+ // Handle OSGi bundles with the default deployer
+ String name = artifact.getName();
+ if (!artifact.canRead()
+ || name.endsWith(".txt") || name.endsWith(".xml")
+ || name.endsWith(".properties") || name.endsWith(".cfg"))
+ {
+ // that's file type which is not supported as bundle and avoid
+ // exception in the log
+ return false;
+ }
+ jar = new JarFile(artifact);
+ Manifest m = jar.getManifest();
+ if (m.getMainAttributes().getValue(new Attributes.Name("Bundle-SymbolicName")) != null
+ && m.getMainAttributes().getValue(new Attributes.Name("Bundle-Version")) != null)
+ {
+ return true;
+ }
+ }
+ catch (Exception e)
+ {
+ // Ignore
+ }
+ finally
+ {
+ if (jar != null)
+ {
+ try
+ {
+ jar.close();
+ }
+ catch (IOException e)
+ {
+ // Ignore
+ }
+ }
+ }
+ return false;
+ }
+
+ public File transform(File artifact, File tmpDir) {
+ return artifact;
+ }
+
+}
Added: felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/ConfigInstaller.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/ConfigInstaller.java?rev=809470&view=auto
==============================================================================
--- felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/ConfigInstaller.java (added)
+++ felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/ConfigInstaller.java Mon Aug 31 06:40:43 2009
@@ -0,0 +1,174 @@
+/*
+ * 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.felix.fileinstall;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.FileInputStream;
+import java.util.Properties;
+import java.util.Hashtable;
+
+import org.apache.felix.fileinstall.listener.ArtifactInstaller;
+import org.apache.felix.fileinstall.util.Util;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.Configuration;
+
+/**
+ * ArtifactInstaller for configurations.
+ * TODO: This service lifecycle should be bound to the ConfigurationAdmin service lifecycle.
+ */
+public class ConfigInstaller implements ArtifactInstaller
+{
+ BundleContext context;
+
+ ConfigInstaller(BundleContext context) {
+ this.context = context;
+ }
+
+ public boolean canHandle(File artifact) {
+ return artifact.getName().endsWith(".cfg");
+ }
+
+ public void install(File artifact) throws Exception {
+ setConfig(artifact);
+ }
+
+ public void update(File artifact) throws Exception {
+ setConfig(artifact);
+ }
+
+ public void uninstall(File artifact) throws Exception {
+ deleteConfig(artifact);
+ }
+
+ /**
+ * Set the configuration based on the config file.
+ *
+ * @param f
+ * Configuration file
+ * @return
+ * @throws Exception
+ */
+ boolean setConfig(File f) throws Exception
+ {
+ Properties p = new Properties();
+ InputStream in = new FileInputStream(f);
+ try
+ {
+ p.load(in);
+ }
+ finally
+ {
+ in.close();
+ }
+ Util.performSubstitution(p);
+ String pid[] = parsePid(f.getName());
+ Hashtable ht = new Hashtable();
+ ht.putAll(p);
+ ht.put(DirectoryWatcher.FILENAME, f.getName());
+ Configuration config = getConfiguration(pid[0], pid[1]);
+ if (config.getBundleLocation() != null)
+ {
+ config.setBundleLocation(null);
+ }
+ config.update(ht);
+ return true;
+ }
+
+ /**
+ * Remove the configuration.
+ *
+ * @param f
+ * File where the configuration in whas defined.
+ * @return
+ * @throws Exception
+ */
+ boolean deleteConfig(File f) throws Exception
+ {
+ String pid[] = parsePid(f.getName());
+ Configuration config = getConfiguration(pid[0], pid[1]);
+ config.delete();
+ return true;
+ }
+
+ String[] parsePid(String path)
+ {
+ String pid = path.substring(0, path.length() - 4);
+ int n = pid.indexOf('-');
+ if (n > 0)
+ {
+ String factoryPid = pid.substring(n + 1);
+ pid = pid.substring(0, n);
+ return new String[]
+ {
+ pid, factoryPid
+ };
+ }
+ else
+ {
+ return new String[]
+ {
+ pid, null
+ };
+ }
+ }
+
+ Configuration getConfiguration(String pid, String factoryPid)
+ throws Exception
+ {
+ Configuration oldConfiguration = findExistingConfiguration(pid, factoryPid);
+ if (oldConfiguration != null)
+ {
+ Util.log(context, 0, "Updating configuration from " + pid
+ + (factoryPid == null ? "" : "-" + factoryPid) + ".cfg", null);
+ return oldConfiguration;
+ }
+ else
+ {
+ Configuration newConfiguration;
+ if (factoryPid != null)
+ {
+ newConfiguration = FileInstall.getConfigurationAdmin().createFactoryConfiguration(pid, null);
+ }
+ else
+ {
+ newConfiguration = FileInstall.getConfigurationAdmin().getConfiguration(pid, null);
+ }
+ return newConfiguration;
+ }
+ }
+
+ Configuration findExistingConfiguration(String pid, String factoryPid) throws Exception
+ {
+ String suffix = factoryPid == null ? ".cfg" : "-" + factoryPid + ".cfg";
+
+ String filter = "(" + DirectoryWatcher.FILENAME + "=" + pid + suffix + ")";
+ Configuration[] configurations = FileInstall.getConfigurationAdmin().listConfigurations(filter);
+ if (configurations != null && configurations.length > 0)
+ {
+ return configurations[0];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+}
Modified: felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/DirectoryWatcher.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/DirectoryWatcher.java?rev=809470&r1=809469&r2=809470&view=diff
==============================================================================
--- felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/DirectoryWatcher.java (original)
+++ felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/DirectoryWatcher.java Mon Aug 31 06:40:43 2009
@@ -21,12 +21,16 @@
import java.io.*;
import java.util.*;
import java.net.URISyntaxException;
+import java.net.URI;
import org.apache.felix.fileinstall.util.Util;
+import org.apache.felix.fileinstall.listener.ArtifactInstaller;
+import org.apache.felix.fileinstall.listener.ArtifactTransformer;
+import org.apache.felix.fileinstall.listener.ArtifactListener;
import org.osgi.framework.*;
-import org.osgi.service.cm.*;
-import org.osgi.service.log.*;
import org.osgi.service.packageadmin.*;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
/**
* -DirectoryWatcher-
@@ -57,50 +61,62 @@
public final static String POLL = "felix.fileinstall.poll";
public final static String DIR = "felix.fileinstall.dir";
public final static String DEBUG = "felix.fileinstall.debug";
+ public final static String TMPDIR = "felix.fileinstall.tmpdir";
public final static String FILTER = "felix.fileinstall.filter";
- public final static String START_NEW_BUNDLES =
- "felix.fileinstall.bundles.new.start";
+ public final static String START_NEW_BUNDLES = "felix.fileinstall.bundles.new.start";
+
File watchedDirectory;
- long poll = 2000;
+ File tmpDir;
+ long poll;
long debug;
+ boolean startBundles;
String filter;
- boolean startBundles = true; // by default, we start bundles.
BundleContext context;
- boolean reported;
String originatingFileName;
-
- Map/* <String, Jar> */ currentManagedBundles = new HashMap();
- // Represents jars that could not be installed
- Map/* <String, Jar> */ installationFailures = new HashMap();
+ // Map of all installed artifacts
+ Map/* <File, Artifact> */ currentManagedArtifacts = new HashMap/* <File, Artifact> */();
+
+ // The scanner to report files changes
+ Scanner scanner;
+
+ // Represents files that could not be processed because of a missing artifact listener
+ Set/* <File> */ processingFailures = new HashSet/* <File> */();
+
+ // Represents artifacts that could not be installed
+ Map/* <File, Artifact> */ installationFailures = new HashMap/* <File, Artifact> */();
- // Represents jars that could not be installed
- Set/* <Bundle> */ startupFailures = new HashSet();
+ // Represents artifacts that could not be installed
+ Set/* <Bundle> */ startupFailures = new HashSet/* <Bundle> */();
public DirectoryWatcher(Dictionary properties, BundleContext context)
{
super(properties.toString());
this.context = context;
- poll = getLong(properties, POLL, poll);
+ poll = getLong(properties, POLL, 2000);
debug = getLong(properties, DEBUG, -1);
originatingFileName = (String) properties.get(FILENAME);
-
- String dir = (String) properties.get(DIR);
- if (dir == null)
- {
- dir = "./load";
- }
- watchedDirectory = new File(dir);
-
- prepareWatchedDir(watchedDirectory);
-
- Object value = properties.get(START_NEW_BUNDLES);
- if (value != null)
+ watchedDirectory = getFile(properties, DIR, new File("./load"));
+ prepareDir(watchedDirectory);
+ tmpDir = getFile(properties, TMPDIR, new File("./tmp"));
+ startBundles = getBoolean(properties, START_NEW_BUNDLES, true); // by default, we start bundles.
+ filter = (String) properties.get(FILTER);
+
+ FilenameFilter flt;
+ if (filter != null && filter.length() > 0)
{
- startBundles = "true".equalsIgnoreCase((String)value);
+ flt = new FilenameFilter()
+ {
+ public boolean accept(File dir, String name) {
+ return name.matches(filter);
+ }
+ };
}
-
- filter = (String) properties.get(FILTER);
+ else
+ {
+ flt = null;
+ }
+ scanner = new Scanner(watchedDirectory, flt);
}
/**
@@ -113,34 +129,162 @@
log("{" + POLL + " (ms) = " + poll + ", "
+ DIR + " = " + watchedDirectory.getAbsolutePath() + ", "
+ DEBUG + " = " + debug + ", "
- + FILTER + " = " + filter + ", "
- + START_NEW_BUNDLES + " = " + startBundles + "}", null);
+ + START_NEW_BUNDLES + " = " + startBundles + ", "
+ + TMPDIR + " = " + tmpDir + ", "
+ + FILTER + " = " + filter + "}", null);
+
initializeCurrentManagedBundles();
- Map currentManagedConfigs = new HashMap(); // location -> Long(time)
- FilenameFilter flt;
- if (filter != null)
- {
- flt = new FilenameFilter()
- {
- public boolean accept(File dir, String name) {
- return name.matches(filter);
- }
- };
- }
- else
- {
- flt = null;
- }
+ scanner.initialize(currentManagedArtifacts.keySet());
+
while (!interrupted())
{
try
{
- Map/* <String, Jar> */ installed = new HashMap();
- Set/* <String> */ configs = new HashSet();
- traverse(installed, configs, watchedDirectory, flt);
- doInstalled(installed);
- doConfigs(currentManagedConfigs, configs);
+ Set/*<File>*/ files = scanner.scan();
+ List/*<ArtifactListener>*/ listeners = FileInstall.getListeners();
+ List/*<Artifact>*/ deleted = new ArrayList/*<Artifact>*/();
+ List/*<Artifact>*/ modified = new ArrayList/*<Artifact>*/();
+ List/*<Artifact>*/ created = new ArrayList/*<Artifact>*/();
+
+ // Try to process again files that could not be processed
+ files.addAll(processingFailures);
+ processingFailures.clear();
+
+ for (Iterator it = files.iterator(); it.hasNext();)
+ {
+ File file = (File) it.next();
+ boolean exists = file.exists();
+ Artifact artifact = (Artifact) currentManagedArtifacts.get(file);
+ // File has been deleted
+ if (!exists && artifact != null)
+ {
+ deleteJaredDirectory(artifact);
+ deleteTransformedFile(artifact);
+ deleted.add(artifact);
+ }
+ else
+ {
+ File jar = file;
+ // Jar up the directory if needed
+ if (file.isDirectory())
+ {
+ prepareDir(tmpDir);
+ try
+ {
+ jar = new File(tmpDir, file.getName() + ".jar");
+ Util.jarDir(file, jar);
+
+ }
+ catch (IOException e)
+ {
+ log("Unable to create jar for: " + file.getAbsolutePath(), e);
+ continue;
+ }
+ }
+ // File has been modified
+ if (exists && artifact != null)
+ {
+ // Check the last modified date against
+ // the artifact last modified date if available. This will loose
+ // the possibility of the jar being replaced by an older one
+ // or the content changed without the date being modified, but
+ // else, we'd have to reinstall all the deployed bundles on restart.
+ if (artifact.getLastModified() > Util.getLastModified(file))
+ {
+ continue;
+ }
+ // If there's no listener, this is because this artifact has been installed before
+ // fileinstall has been restarted. In this case, try to find a listener.
+ if (artifact.getListener() == null)
+ {
+ ArtifactListener listener = findListener(jar, listeners);
+ // If no listener can handle this artifact, we need to defer the
+ // processing for this artifact until one is found
+ if (listener == null)
+ {
+ processingFailures.add(file);
+ continue;
+ }
+ artifact.setListener(listener);
+ }
+ // If the listener can not handle this file anymore,
+ // uninstall the artifact and try as if is was new
+ if (!listeners.contains(artifact.getListener()) || !artifact.getListener().canHandle(jar))
+ {
+ deleted.add(artifact);
+ artifact = null;
+ }
+ // The listener is still ok
+ else
+ {
+ deleteTransformedFile(artifact);
+ artifact.setJaredDirectory(jar);
+ if (transformArtifact(artifact))
+ {
+ modified.add(artifact);
+ }
+ else
+ {
+ deleteJaredDirectory(artifact);
+ deleted.add(artifact);
+ }
+ continue;
+ }
+ }
+ // File has been added
+ if (exists && artifact == null)
+ {
+ // Find the listener
+ ArtifactListener listener = findListener(jar, listeners);
+ // If no listener can handle this artifact, we need to defer the
+ // processing for this artifact until one is found
+ if (listener == null)
+ {
+ processingFailures.add(file);
+ continue;
+ }
+ // Create the artifact
+ artifact = new Artifact();
+ artifact.setPath(file);
+ artifact.setJaredDirectory(jar);
+ artifact.setListener(listener);
+ if (transformArtifact(artifact))
+ {
+ created.add(artifact);
+ }
+ else
+ {
+ deleteJaredDirectory(artifact);
+ }
+ }
+ }
+ }
+ // Handle deleted artifacts
+ // We do the operations in the following order:
+ // uninstall, update, install, refresh & start.
+ Collection uninstalledBundles = uninstall(deleted);
+ Collection updatedBundles = update(modified);
+ Collection installedBundles = install(created);
+ if (uninstalledBundles.size() > 0 || updatedBundles.size() > 0)
+ {
+ // Refresh if any bundle got uninstalled or updated.
+ // This can lead to restart of recently updated bundles, but
+ // don't worry about that at this point of time.
+ refresh();
+ }
+
+ if (startBundles)
+ {
+ // Try to start all the bundles that we could not start last time.
+ // Make a copy, because start() changes the underlying collection
+ start(new HashSet(startupFailures));
+ // Start updated bundles.
+ start(updatedBundles);
+ // Start newly installed bundles
+ start(installedBundles);
+ }
+
Thread.sleep(poll);
}
catch (InterruptedException e)
@@ -153,283 +297,85 @@
}
}
}
-
- /**
- * Create the watched directory, if not existing.
- * Throws a runtime exception if the directory cannot be created,
- * or if the provided File parameter does not refer to a directory.
- *
- * @param watchedDirectory
- * The directory File Install will monitor
- */
- private void prepareWatchedDir(File watchedDirectory)
- {
- if (!watchedDirectory.exists() && !watchedDirectory.mkdirs())
- {
- log("Cannot create folder "
- + watchedDirectory
- + ". Is the folder write-protected?", null);
- throw new RuntimeException("Cannot create folder: " + watchedDirectory);
- }
- if (!watchedDirectory.isDirectory())
+ ArtifactListener findListener(File artifact, List/* <ArtifactListener> */ listeners)
+ {
+ for (Iterator itL = listeners.iterator(); itL.hasNext();)
{
- log("Cannot watch "
- + watchedDirectory
- + " because it's not a directory", null);
- throw new RuntimeException(
- "Cannot start FileInstall to watch something that is not a directory");
+ ArtifactListener listener = (ArtifactListener) itL.next();
+ if (listener.canHandle(artifact))
+ {
+ return listener;
+ }
}
+ return null;
}
- /**
- * Handle the changes between the configurations already installed and the
- * newly found/lost configurations.
- *
- * @param current
- * Existing installed configurations abspath -> File
- * @param discovered
- * Newly found configurations
- */
- void doConfigs(Map current, Set discovered)
- {
- try
+ boolean transformArtifact(Artifact artifact) {
+ if (artifact.getListener() instanceof ArtifactTransformer)
{
- // Set all old keys as inactive, we remove them
- // when we find them to be active, will be left
- // with the inactive ones.
- Set inactive = new HashSet(current.keySet());
-
- for (Iterator e = discovered.iterator(); e.hasNext(); )
+ prepareDir(tmpDir);
+ try
{
- String path = (String) e.next();
- File f = new File(path);
-
- if (!current.containsKey(path))
+ File transformed = ((ArtifactTransformer) artifact.getListener()).transform(artifact.getJaredDirectory(), tmpDir);
+ if (transformed != null)
{
- // newly found entry, set the config immedialey
- Long l = new Long(f.lastModified());
- if (setConfig(f))
- {
- // Remember it for the next round
- current.put(path, l);
- }
+ artifact.setTransformed(transformed);
+ return true;
}
- else
- {
- // Found an existing one.
- // Check if it has been updated
- long lastModified = f.lastModified();
- long oldTime = ((Long) current.get(path)).longValue();
- if (oldTime < lastModified)
- {
- if (setConfig(f))
- {
- // Remember it for the next round.
- current.put(path, new Long(lastModified));
- }
- }
- }
- // Mark this one as active
- inactive.remove(path);
}
- for (Iterator e = inactive.iterator(); e.hasNext();)
+ catch (Exception e)
{
- String path = (String) e.next();
- File f = new File(path);
- if (deleteConfig(f))
- {
- current.remove(path);
- }
+ log("Unable to transform artifact: " + artifact.getPath().getAbsolutePath(), e);
}
+ return false;
}
- catch (Exception ee)
- {
- log("Processing config: ", ee);
- }
+ return true;
}
- /**
- * Set the configuration based on the config file.
- *
- * @param f
- * Configuration file
- * @return
- * @throws Exception
- */
- boolean setConfig(File f) throws Exception
- {
- ConfigurationAdmin cm = (ConfigurationAdmin) FileInstall.cmTracker.getService();
- if (cm == null)
+ private void deleteTransformedFile(Artifact artifact) {
+ if (artifact.getTransformed() != null
+ && !artifact.getTransformed().equals(artifact.getPath())
+ && !artifact.getTransformed().delete())
{
- if (debug != 0 && !reported)
- {
- log("Can't find a Configuration Manager, configurations do not work",
- null);
- reported = true;
- }
- return false;
+ log("Unable to delete transformed artifact: " + artifact.getTransformed().getAbsolutePath(), null);
}
+ }
- Properties p = new Properties();
- InputStream in = new FileInputStream(f);
- try
+ private void deleteJaredDirectory(Artifact artifact) {
+ if (artifact.getJaredDirectory() != null
+ && !artifact.getJaredDirectory().equals(artifact.getPath())
+ && !artifact.getJaredDirectory().delete())
{
- p.load(in);
+ log("Unable to delete jared artifact: " + artifact.getJaredDirectory().getAbsolutePath(), null);
}
- finally
- {
- in.close();
- }
- for (Enumeration e = p.keys(); e.hasMoreElements(); )
- {
- String name = (String) e.nextElement();
- Object value = p.get(name);
- p.put(name,
- value instanceof String
- ? Util.substVars((String) value, name, null, p)
- : value);
- }
- String pid[] = parsePid(f.getName());
- Hashtable ht = new Hashtable();
- ht.putAll(p);
- ht.put(FILENAME, f.getName());
- Configuration config = getConfiguration(pid[0], pid[1]);
- if (config.getBundleLocation() != null)
- {
- config.setBundleLocation(null);
- }
- config.update(ht);
- return true;
}
/**
- * Remove the configuration.
+ * Create the watched directory, if not existing.
+ * Throws a runtime exception if the directory cannot be created,
+ * or if the provided File parameter does not refer to a directory.
*
- * @param f
- * File where the configuration in whas defined.
- * @return
- * @throws Exception
+ * @param dir
+ * The directory File Install will monitor
*/
- boolean deleteConfig(File f) throws Exception
- {
- String pid[] = parsePid(f.getName());
- Configuration config = getConfiguration(pid[0], pid[1]);
- config.delete();
- return true;
- }
-
- String[] parsePid(String path)
- {
- String pid = path.substring(0, path.length() - 4);
- int n = pid.indexOf('-');
- if (n > 0)
- {
- String factoryPid = pid.substring(n + 1);
- pid = pid.substring(0, n);
- return new String[]
- {
- pid, factoryPid
- };
- }
- else
- {
- return new String[]
- {
- pid, null
- };
- }
- }
-
- Configuration getConfiguration(String pid, String factoryPid)
- throws Exception
+ private void prepareDir(File dir)
{
- Configuration oldConfiguration = findExistingConfiguration(pid, factoryPid);
- if (oldConfiguration != null)
+ if (!dir.exists() && !dir.mkdirs())
{
- log("Updating configuration from " + pid
- + (factoryPid == null ? "" : "-" + factoryPid) + ".cfg", null);
- return oldConfiguration;
- }
- else
- {
- ConfigurationAdmin cm = (ConfigurationAdmin) FileInstall.cmTracker.getService();
- Configuration newConfiguration = null;
- if (factoryPid != null)
- {
- newConfiguration = cm.createFactoryConfiguration(pid, null);
- }
- else
- {
- newConfiguration = cm.getConfiguration(pid, null);
- }
- return newConfiguration;
+ log("Cannot create folder "
+ + dir
+ + ". Is the folder write-protected?", null);
+ throw new RuntimeException("Cannot create folder: " + dir);
}
- }
-
- Configuration findExistingConfiguration(String pid, String factoryPid) throws Exception
- {
- String suffix = factoryPid == null ? ".cfg" : "-" + factoryPid + ".cfg";
- ConfigurationAdmin cm = (ConfigurationAdmin) FileInstall.cmTracker.getService();
- String filter = "(" + FILENAME + "=" + pid + suffix + ")";
- Configuration[] configurations = cm.listConfigurations(filter);
- if (configurations != null && configurations.length > 0)
- {
- return configurations[0];
- }
- else
+ if (!dir.isDirectory())
{
- return null;
- }
- }
-
- /**
- * This is the core of this class.
- * Install bundles that were discovered, uninstall bundles that are gone
- * from the current state and update the ones that have been changed.
- * Keep {@link #currentManagedBundles} up-to-date.
- *
- * @param discovered
- * A map of path to {@link Jar} that holds the discovered state
- */
- void doInstalled(Map discovered)
- {
- // Find out all the new, deleted and common bundles.
- // new = discovered - current,
- Set newBundles = new HashSet(discovered.values());
- newBundles.removeAll(currentManagedBundles.values());
-
- // deleted = current - discovered
- Set deletedBundles = new HashSet(currentManagedBundles.values());
- deletedBundles.removeAll(discovered.values());
-
- // existing = intersection of current & discovered
- Set existingBundles = new HashSet(discovered.values());
- existingBundles.retainAll(currentManagedBundles.values());
-
- // We do the operations in the following order:
- // uninstall, update, install, refresh & start.
- Collection uninstalledBundles = uninstall(deletedBundles);
- Collection updatedBundles = update(existingBundles);
- Collection installedBundles = install(newBundles);
- if (uninstalledBundles.size() > 0 || updatedBundles.size() > 0)
- {
- // Refresh if any bundle got uninstalled or updated.
- // This can lead to restart of recently updated bundles, but
- // don't worry about that at this point of time.
- refresh();
- }
-
- if (startBundles)
- {
- // Try to start all the bundles that we could not start last time.
- // Make a copy, because start() changes the underlying collection
- start(new HashSet(startupFailures));
- // Start updated bundles.
- start(updatedBundles);
- // Start newly installed bundles.
- start(installedBundles);
+ log("Cannot use "
+ + dir
+ + " because it's not a directory", null);
+ throw new RuntimeException(
+ "Cannot start FileInstall using something that is not a directory");
}
}
@@ -444,150 +390,94 @@
*/
void log(String message, Throwable e)
{
- LogService log = getLogService();
- if (log == null)
- {
- System.out.println(message + (e == null ? "" : ": " + e));
- if (debug > 0 && e != null)
- {
- e.printStackTrace(System.out);
- }
- }
- else
- {
- if (e != null)
- {
- log.log(LogService.LOG_ERROR, message, e);
- if (debug > 0 && e != null)
- {
- e.printStackTrace();
- }
- }
- else
- {
- log.log(LogService.LOG_INFO, message);
- }
- }
+ Util.log(context, debug, message, e);
}
/**
- * Answer the Log Service
+ * Check if a bundle is a fragment.
*
+ * @param bundle
* @return
*/
- LogService getLogService()
+ boolean isFragment(Bundle bundle)
{
- ServiceReference ref = context.getServiceReference(LogService.class.getName());
- if (ref != null)
+ PackageAdmin padmin = FileInstall.getPackageAdmin();
+ if (padmin != null)
{
- LogService log = (LogService) context.getService(ref);
- return log;
+ return padmin.getBundleType(bundle) == PackageAdmin.BUNDLE_TYPE_FRAGMENT;
}
- return null;
+ return false;
}
/**
- * Traverse the directory and fill the set with the found jars and
- * configurations.
- *
- * @param jars
- * Returns path -> {@link Jar} map for found jars
- * @param configs
- * Returns the abspath -> file for found configurations
- * @param jardir
- * The directory to traverse
- * @param filter
- * A filter for file names
+ * Convenience to refresh the packages
*/
- void traverse(Map/* <String, Jar> */ jars, Set configs, File jardir, FilenameFilter filter)
+ void refresh()
{
- String list[] = jardir.list(filter);
- if (list == null)
+ PackageAdmin padmin = FileInstall.getPackageAdmin();
+ if (padmin != null)
{
- prepareWatchedDir(jardir);
- list = jardir.list(filter);
- }
- for (int i = 0; (list != null) && (i < list.length); i++)
- {
- File file = new File(jardir, list[i]);
- if (list[i].endsWith(".cfg"))
- {
- configs.add(file.getAbsolutePath());
- }
- else if (Util.isValidJar(file.getAbsolutePath()))
- {
- Jar jar = new Jar(file);
- jars.put(jar.getPath(), jar);
- }
+ padmin.refreshPackages(null);
}
}
/**
- * Check if a bundle is a fragment.
+ * Retrieve a property as a long.
*
- * @param bundle
- * @return
+ * @param properties the properties to retrieve the value from
+ * @param property the name of the property to retrieve
+ * @param dflt the default value
+ * @return the property as a long or the default value
*/
- boolean isFragment(Bundle bundle)
+ long getLong(Dictionary properties, String property, long dflt)
{
- PackageAdmin padmin;
- if (FileInstall.padmin == null)
- {
- return false;
- }
-
- try
+ String value = (String) properties.get(property);
+ if (value != null)
{
- padmin = (PackageAdmin) FileInstall.padmin.waitForService(10000);
- if (padmin != null)
+ try
{
- return padmin.getBundleType(bundle) == PackageAdmin.BUNDLE_TYPE_FRAGMENT;
+ return Long.parseLong(value);
+ }
+ catch (Exception e)
+ {
+ log(property + " set, but not a long: " + value, null);
}
}
- catch (InterruptedException e)
- {
- // stupid exception
- }
- return false;
+ return dflt;
}
/**
- * Convenience to refresh the packages
+ * Retrieve a property as a File.
+ *
+ * @param properties the properties to retrieve the value from
+ * @param property the name of the property to retrieve
+ * @param dflt the default value
+ * @return the property as a File or the default value
*/
- void refresh()
+ File getFile(Dictionary properties, String property, File dflt)
{
- PackageAdmin padmin;
- try
- {
- padmin = (PackageAdmin) FileInstall.padmin.waitForService(10000);
- padmin.refreshPackages(null);
- }
- catch (InterruptedException e)
+ String value = (String) properties.get(property);
+ if (value != null)
{
- Thread.currentThread().interrupt();
+ return new File(value);
}
+ return dflt;
}
/**
- * Answer the long from a property.
+ * Retrieve a property as a boolan.
*
- * @param property
- * @param dflt
- * @return
+ * @param properties the properties to retrieve the value from
+ * @param property the name of the property to retrieve
+ * @param dflt the default value
+ * @return the property as a boolean or the default value
*/
- long getLong(Dictionary properties, String property, long dflt)
+ boolean getBoolean(Dictionary properties, String property, boolean dflt)
{
String value = (String) properties.get(property);
if (value != null)
{
- try
- {
- return Long.parseLong(value);
- }
- catch (Exception e)
- {
- log(property + " set, but not a long: " + value, null);
- }
+ return Boolean.parseBoolean(value);
}
return dflt;
}
@@ -616,46 +506,62 @@
String watchedDirPath = watchedDirectory.toURI().normalize().getPath();
for (int i = 0; i < bundles.length; i++)
{
- try
+ Artifact artifact = new Artifact();
+ artifact.setBundleId(bundles[i].getBundleId());
+ artifact.setLastModified(bundles[i].getLastModified());
+ artifact.setListener(null);
+ // Convert to a URI because the location of a bundle
+ // is typically a URI. At least, that's the case for
+ // autostart bundles and bundles installed by fileinstall.
+ // Normalisation is needed to ensure that we don't treat (e.g.)
+ // /tmp/foo and /tmp//foo differently.
+ String location = bundles[i].getLocation();
+ String path = null;
+ if (location != null &&
+ !location.equals(Constants.SYSTEM_BUNDLE_LOCATION))
{
- Jar jar = new Jar(bundles[i]);
- String path = jar.getPath();
- if (path == null)
+ URI uri;
+ try
{
- // jar.getPath is null means we could not parse the location
- // as a meaningful URI or file path. e.g., location
- // represented an Opaque URI.
- // We can't do any meaningful processing for this bundle.
- continue;
+ uri = new URI(bundles[i].getLocation()).normalize();
}
- final int index = path.lastIndexOf('/');
- if (index != -1 && path.substring(0, index + 1).equals(watchedDirPath))
+ catch (URISyntaxException e)
{
- currentManagedBundles.put(path, jar);
+ // Let's try to interpret the location as a file path
+ uri = new File(location).toURI().normalize();
}
+ path = uri.getPath();
}
- catch (URISyntaxException e)
+ if (path == null)
{
- // Ignore and continue.
- // This can never happen for bundles that have been installed
- // by FileInstall, as we always use proper filepath as location.
+ // jar.getPath is null means we could not parse the location
+ // as a meaningful URI or file path. e.g., location
+ // represented an Opaque URI.
+ // We can't do any meaningful processing for this bundle.
+ continue;
+ }
+ artifact.setPath(new File(path));
+ final int index = path.lastIndexOf('/');
+ if (index != -1 && path.startsWith(watchedDirPath))
+ {
+ currentManagedArtifacts.put(new File(path), artifact);
}
}
}
/**
- * This method installs a collection of jar files.
- * @param jars Collection of {@link Jar} to be installed
+ * This method installs a collection of artifacts.
+ * @param artifacts Collection of {@link Artifact}s to be installed
* @return List of Bundles just installed
*/
- private Collection/* <Bundle> */ install(Collection jars)
+ private Collection/* <Bundle> */ install(Collection/* <Artifact> */ artifacts)
{
List bundles = new ArrayList();
- for (Iterator iter = jars.iterator(); iter.hasNext();)
+ for (Iterator iter = artifacts.iterator(); iter.hasNext();)
{
- Jar jar = (Jar) iter.next();
+ Artifact artifact = (Artifact) iter.next();
- Bundle bundle = install(jar);
+ Bundle bundle = install(artifact);
if (bundle != null)
{
bundles.add(bundle);
@@ -665,16 +571,17 @@
}
/**
- * @param jars Collection of {@link Jar} to be uninstalled
+ * This method uninstalls a collection of artifacts.
+ * @param artifacts Collection of {@link Artifact}s to be uninstalled
* @return Collection of Bundles that got uninstalled
*/
- private Collection/* <Bundle> */ uninstall(Collection jars)
+ private Collection/* <Bundle> */ uninstall(Collection/* <Artifact> */ artifacts)
{
List bundles = new ArrayList();
- for (Iterator iter = jars.iterator(); iter.hasNext();)
+ for (Iterator iter = artifacts.iterator(); iter.hasNext();)
{
- final Jar jar = (Jar) iter.next();
- Bundle b = uninstall(jar);
+ final Artifact artifact = (Artifact) iter.next();
+ Bundle b = uninstall(artifact);
if (b != null)
{
bundles.add(b);
@@ -683,45 +590,30 @@
return bundles;
}
- private void start(Collection bundles)
- {
- for (Iterator b = bundles.iterator(); b.hasNext(); )
- {
- start((Bundle) b.next());
- }
- }
-
/**
- * Update the bundles if the underlying files have changed.
- * This method reads the information about jars to be updated,
- * compares them with information available in {@link #currentManagedBundles}.
- * If the file is newer, it updates the bundle.
+ * This method updates a collection of artifacts.
*
- * @param jars Collection of {@link Jar}s representing state of files.
+ * @param artifacts Collection of {@link Artifact}s to be updated.
* @return Collection of bundles that got updated
*/
- private Collection/* <Bundle> */ update(Collection jars)
+ private Collection/* <Bundle> */ update(Collection/* <Artifact> */ artifacts)
{
List bundles = new ArrayList();
- for (Iterator iter = jars.iterator(); iter.hasNext(); )
+ for (Iterator iter = artifacts.iterator(); iter.hasNext(); )
{
- Jar e = (Jar) iter.next();
- Jar c = (Jar) currentManagedBundles.get(e.getPath());
- if (e.isNewer(c))
+ Artifact e = (Artifact) iter.next();
+ Bundle b = update(e);
+ if (b != null)
{
- Bundle b = update(c);
- if (b != null)
- {
- bundles.add(b);
- }
+ bundles.add(b);
}
}
return bundles;
}
/**
- * Install a jar and return the bundle object.
- * It uses {@link org.apache.felix.fileinstall.Jar#getPath()} as location
+ * Install an artifact and return the bundle object.
+ * It uses {@link org.apache.felix.fileinstall.Artifact#getPath()} as location
* of the new bundle. Before installing a file,
* it sees if the file has been identified as a bad file in
* earlier run. If yes, then it compares to see if the file has changed
@@ -729,44 +621,55 @@
* If the file has not been identified as a bad file in earlier run,
* then it always installs it.
*
- * @param jar the jar to be installed
+ * @param artifact the artifact to be installed
* @return Bundle object that was installed
*/
- private Bundle install(Jar jar)
+ private Bundle install(Artifact artifact)
{
Bundle bundle = null;
try
{
- String path = jar.getPath();
- Jar badJar = (Jar) installationFailures.get(jar.getPath());
- if (badJar != null && badJar.getLastModified() == jar.getLastModified())
- {
- return null; // Don't attempt to install it; nothing has changed.
- }
- File file = new File(path);
- InputStream in = new FileInputStream(file);
- try
+ File path = artifact.getPath();
+ // If the listener is an installer, ask for an update
+ if (artifact.getListener() instanceof ArtifactInstaller)
{
- // Some users wanted the location to be a URI (See FELIX-1269)
- final String location = file.toURI().normalize().toString();
- bundle = context.installBundle(location, in);
+ ((ArtifactInstaller) artifact.getListener()).install(path);
}
- finally
+ // else we need to ask for an update on the bundle
+ else if (artifact.getListener() instanceof ArtifactTransformer)
{
- in.close();
+ File transformed = artifact.getTransformed();
+ Artifact badArtifact = (Artifact) installationFailures.get(artifact.getPath());
+ if (badArtifact != null && badArtifact.getLastModified() == artifact.getLastModified())
+ {
+ return null; // Don't attempt to install it; nothing has changed.
+ }
+ InputStream in = new FileInputStream(transformed != null ? transformed : path);
+ try
+ {
+ // Some users wanted the location to be a URI (See FELIX-1269)
+ final String location = path.toURI().normalize().toString();
+ bundle = context.installBundle(location, in);
+ }
+ finally
+ {
+ in.close();
+ }
+ artifact.setBundleId(bundle.getBundleId());
}
+ artifact.setLastModified(Util.getLastModified(path));
installationFailures.remove(path);
- currentManagedBundles.put(path, new Jar(bundle));
- log("Installed " + file.getAbsolutePath(), null);
+ currentManagedArtifacts.put(path, artifact);
+ log("Installed " + path, null);
}
catch (Exception e)
{
- log("Failed to install bundle: " + jar.getPath(), e);
+ log("Failed to install artifact: " + artifact.getPath(), e);
// Add it our bad jars list, so that we don't
// attempt to install it again and again until the underlying
// jar has been modified.
- installationFailures.put(jar.getPath(), jar);
+ installationFailures.put(artifact.getPath(), artifact);
}
return bundle;
}
@@ -774,74 +677,97 @@
/**
* Uninstall a jar file.
*/
- private Bundle uninstall(Jar jar)
+ private Bundle uninstall(Artifact artifact)
{
+ Bundle bundle = null;
try
{
- Jar old = (Jar) currentManagedBundles.remove(jar.getPath());
-
- // old can't be null because of the way we calculate deleted list.
- Bundle bundle = context.getBundle(old.getBundleId());
- if (bundle == null)
- {
- log("Failed to uninstall bundle: "
- + jar.getPath() + " with id: "
- + old.getBundleId()
- + ". The bundle has already been uninstalled", null);
- return null;
+ File path = artifact.getPath();
+ // Forget this artifact
+ currentManagedArtifacts.remove(path);
+ // Delete transformed file
+ deleteTransformedFile(artifact);
+ // if the listener is an installer, uninstall the artifact
+ if (artifact.getListener() instanceof ArtifactInstaller)
+ {
+ ((ArtifactInstaller) artifact.getListener()).uninstall(path);
+ }
+ // else we need uninstall the bundle
+ else if (artifact.getListener() instanceof ArtifactTransformer)
+ {
+ // old can't be null because of the way we calculate deleted list.
+ bundle = context.getBundle(artifact.getBundleId());
+ if (bundle == null)
+ {
+ log("Failed to uninstall bundle: "
+ + path + " with id: "
+ + artifact.getBundleId()
+ + ". The bundle has already been uninstalled", null);
+ return null;
+ }
+ bundle.uninstall();
}
- bundle.uninstall();
startupFailures.remove(bundle);
- log("Uninstalled " + jar.getPath(), null);
- return bundle;
+ log("Uninstalled " + path, null);
}
catch (Exception e)
{
- log("Failed to uninstall bundle: " + jar.getPath(), e);
+ log("Failed to uninstall artifact: " + artifact.getPath(), e);
}
- return null;
+ return bundle;
}
- private Bundle update(Jar jar)
+ private Bundle update(Artifact artifact)
{
- InputStream in = null;
+ Bundle bundle = null;
try
{
- File file = new File(jar.getPath());
- in = new FileInputStream(file);
- Bundle bundle = context.getBundle(jar.getBundleId());
- if (bundle == null)
- {
- log("Failed to update bundle: "
- + jar.getPath() + " with ID "
- + jar.getBundleId()
- + ". The bundle has been uninstalled", null);
- return null;
+ File path = artifact.getPath();
+ // If the listener is an installer, ask for an update
+ if (artifact.getListener() instanceof ArtifactInstaller)
+ {
+ ((ArtifactInstaller) artifact.getListener()).update(path);
+ }
+ // else we need to ask for an update on the bundle
+ else if (artifact.getListener() instanceof ArtifactTransformer)
+ {
+ File transformed = artifact.getTransformed();
+ bundle = context.getBundle(artifact.getBundleId());
+ if (bundle == null)
+ {
+ log("Failed to update bundle: "
+ + path + " with ID "
+ + artifact.getBundleId()
+ + ". The bundle has been uninstalled", null);
+ return null;
+ }
+ InputStream in = new FileInputStream(transformed != null ? transformed : path);
+ try
+ {
+ bundle.update(in);
+ }
+ finally
+ {
+ in.close();
+ }
}
- bundle.update(in);
startupFailures.remove(bundle);
- jar.setLastModified(bundle.getLastModified());
- log("Updated " + jar.getPath(), null);
- return bundle;
+ artifact.setLastModified(Util.getLastModified(path));
+ log("Updated " + path, null);
}
catch (Exception e)
{
- log("Failed to update bundle " + jar.getPath(), e);
+ log("Failed to update artifact " + artifact.getPath(), e);
}
- finally
+ return bundle;
+ }
+
+ private void start(Collection/* <Bundle> */ bundles)
+ {
+ for (Iterator b = bundles.iterator(); b.hasNext(); )
{
- if (in != null)
- {
- try
- {
- in.close();
- }
- catch (IOException e)
- {
- }
- }
+ start((Bundle) b.next());
}
- return null;
}
private void start(Bundle bundle)
Modified: felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/FileInstall.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/FileInstall.java?rev=809470&r1=809469&r2=809470&view=diff
==============================================================================
--- felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/FileInstall.java (original)
+++ felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/FileInstall.java Mon Aug 31 06:40:43 2009
@@ -21,6 +21,9 @@
import java.util.*;
import org.apache.felix.fileinstall.util.Util;
+import org.apache.felix.fileinstall.listener.ArtifactListener;
+import org.apache.felix.fileinstall.listener.ArtifactInstaller;
+import org.apache.felix.fileinstall.listener.ArtifactTransformer;
import org.osgi.framework.*;
import org.osgi.service.cm.*;
import org.osgi.service.packageadmin.*;
@@ -36,12 +39,16 @@
{
static ServiceTracker padmin;
static ServiceTracker cmTracker;
+ static List /* <ArtifactListener> */ listeners = new ArrayList /* <ArtifactListener> */();
BundleContext context;
Map watchers = new HashMap();
+ ConfigInstaller configInstaller;
+ ServiceTracker listenersTracker;
public void start(BundleContext context) throws Exception
{
this.context = context;
+ addListener(new BundleTransformer());
Hashtable props = new Hashtable();
props.put(Constants.SERVICE_PID, getName());
context.registerService(ManagedServiceFactory.class.getName(), this,
@@ -49,8 +56,39 @@
padmin = new ServiceTracker(context, PackageAdmin.class.getName(), null);
padmin.open();
- cmTracker = new ServiceTracker(context, ConfigurationAdmin.class.getName(), null);
+ cmTracker = new ServiceTracker(context, ConfigurationAdmin.class.getName(), null)
+ {
+ public Object addingService(ServiceReference serviceReference)
+ {
+ ConfigurationAdmin cm = (ConfigurationAdmin) super.addingService(serviceReference);
+ configInstaller = new ConfigInstaller(context);
+ addListener(configInstaller);
+ return cm;
+ }
+ public void removedService(ServiceReference serviceReference, Object o)
+ {
+ configInstaller = null;
+ removeListener(configInstaller);
+ super.removedService(serviceReference, o);
+ }
+ };
cmTracker.open();
+ String flt = "(|(" + Constants.OBJECTCLASS + "=" + ArtifactInstaller.class.getName() + ")"
+ + "(" + Constants.OBJECTCLASS + "=" + ArtifactTransformer.class.getName() + "))";
+ listenersTracker = new ServiceTracker(context, FrameworkUtil.createFilter(flt), null)
+ {
+ public Object addingService(ServiceReference serviceReference)
+ {
+ ArtifactListener listener = (ArtifactListener) super.addingService(serviceReference);
+ addListener(listener);
+ return listener;
+ }
+ public void removedService(ServiceReference serviceReference, Object o)
+ {
+ removeListener((ArtifactListener) o);
+ }
+ };
+ listenersTracker.open();
// Created the initial configuration
Hashtable ht = new Hashtable();
@@ -59,6 +97,7 @@
set(ht, DirectoryWatcher.DIR);
set(ht, DirectoryWatcher.DEBUG);
set(ht, DirectoryWatcher.FILTER);
+ set(ht, DirectoryWatcher.TMPDIR);
set(ht, DirectoryWatcher.START_NEW_BUNDLES);
updated("initial", ht);
}
@@ -93,6 +132,7 @@
// Ignore
}
}
+ listenersTracker.close();
cmTracker.close();
padmin.close();
}
@@ -115,23 +155,71 @@
throws ConfigurationException
{
deleted(pid);
- performSubstitution(properties);
+ Util.performSubstitution(properties);
DirectoryWatcher watcher = new DirectoryWatcher(properties, context);
watchers.put(pid, watcher);
watcher.start();
}
- private void performSubstitution(Dictionary properties)
+ private void addListener(ArtifactListener listener)
{
- for (Enumeration e = properties.keys(); e.hasMoreElements(); )
+ synchronized (listeners)
{
- String name = (String) e.nextElement();
- Object value = properties.get(name);
- properties.put(name,
- value instanceof String
- ? Util.substVars((String) value, name, null, properties)
- : value);
+ listeners.add(listener);
}
}
+
+ private void removeListener(ArtifactListener listener)
+ {
+ synchronized (listeners)
+ {
+ listeners.remove(listener);
+ }
+ }
+
+ static List getListeners()
+ {
+ synchronized (listeners)
+ {
+ return new ArrayList(listeners);
+ }
+ }
+
+ static PackageAdmin getPackageAdmin()
+ {
+ return getPackageAdmin(10000);
+ }
+
+ static PackageAdmin getPackageAdmin(long timeout)
+ {
+ try
+ {
+ return (PackageAdmin) padmin.waitForService(timeout);
+ }
+ catch (InterruptedException e)
+ {
+ Thread.currentThread().interrupt();
+ return null;
+ }
+ }
+
+ static ConfigurationAdmin getConfigurationAdmin()
+ {
+ return getConfigurationAdmin(10000);
+ }
+
+ static ConfigurationAdmin getConfigurationAdmin(long timeout)
+ {
+ try
+ {
+ return (ConfigurationAdmin) cmTracker.waitForService(timeout);
+ }
+ catch (InterruptedException e)
+ {
+ Thread.currentThread().interrupt();
+ return null;
+ }
+ }
+
}
\ No newline at end of file
Added: felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/Scanner.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/Scanner.java?rev=809470&view=auto
==============================================================================
--- felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/Scanner.java (added)
+++ felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/Scanner.java Mon Aug 31 06:40:43 2009
@@ -0,0 +1,180 @@
+/*
+ * 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.felix.fileinstall;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.HashSet;
+import java.util.zip.CRC32;
+import java.io.File;
+import java.io.FilenameFilter;
+
+/**
+ * A Scanner object is able to detect and report new, modified
+ * and deleted files.
+ *
+ * The scanner use an internal checksum to identify the signature
+ * of a file or directory. The checksum will change if the file
+ * or any of the directory's child is modified.
+ *
+ * In addition, if the scanner detects a change on a given file, it
+ * will wait until the checksum does not change anymore before reporting
+ * the change on this file. This allows to not report the change until
+ * a big copy if complete for example.
+ */
+public class Scanner {
+
+ final File directory;
+ final FilenameFilter filter;
+
+ // Store checksums of files or directories
+ Map/* <File, Long> */ lastChecksums = new HashMap/* <File, Long> */();
+ Map/* <File, Long> */ storedChecksums = new HashMap/* <File, Long> */();
+
+ /**
+ * Create a scanner for the specified directory
+ *
+ * @param directory the directory to scan
+ */
+ public Scanner(File directory)
+ {
+ this(directory, null);
+ }
+
+ /**
+ * Create a scanner for the specified directory and file filter
+ *
+ * @param directory the directory to scan
+ * @param filter a filter for file names
+ */
+ public Scanner(File directory, FilenameFilter filter)
+ {
+ this.directory = directory;
+ this.filter = filter;
+ }
+
+ /**
+ * Initialize the list of known files.
+ * This should be called before the first scan to initialize
+ * the list of known files. The purpose is to be able to detect
+ * files that have been deleted while the scanner was inactive.
+ *
+ * @param files a list of known files
+ */
+ public void initialize(Collection/*<File>*/ files)
+ {
+ for (Iterator it = files.iterator(); it.hasNext();)
+ {
+ storedChecksums.put(it.next(), Long.valueOf(0));
+ }
+ }
+
+ /**
+ * Report a set of new, modified or deleted files.
+ * Modifications are checked against a computed checksum on some file
+ * attributes to detect any modification.
+ * Upon restart, such checksums are not known so that all files will
+ * be reported as modified.
+ *
+ * @return a list of changes on the files included in the directory
+ */
+ public Set/*<File>*/ scan()
+ {
+ File[] list = directory.listFiles(filter);
+ if (list == null)
+ {
+ return null;
+ }
+ Set/*<File>*/ files = new HashSet/*<File>*/();
+ Set/*<File>*/ removed = new HashSet/*<File>*/(storedChecksums.keySet());
+ for (int i = 0; i < list.length; i++)
+ {
+ File file = list[i];
+ long lastChecksum = lastChecksums.get(file) != null ? ((Long) lastChecksums.get(file)).longValue() : 0;
+ long storedChecksum = storedChecksums.get(file) != null ? ((Long) storedChecksums.get(file)).longValue() : 0;
+ long newChecksum = checksum(file);
+ lastChecksums.put(file, Long.valueOf(newChecksum));
+ // Only handle file when it does not change anymore and it has changed since last reported
+ if (newChecksum == lastChecksum && newChecksum != storedChecksum)
+ {
+ storedChecksums.put(file, Long.valueOf(newChecksum));
+ files.add(file);
+ }
+ removed.remove(file);
+ }
+ for (Iterator it = removed.iterator(); it.hasNext();)
+ {
+ File file = (File) it.next();
+ // Make sure we'll handle a file that has been deleted
+ files.addAll(removed);
+ // Remove no longer used checksums
+ lastChecksums.remove(file);
+ storedChecksums.remove(file);
+ }
+ return files;
+ }
+
+ /**
+ * Compute a cheksum for the file or directory that consists of the name, length and the last modified date
+ * for a file and its children in case of a directory
+ *
+ * @param file the file or directory
+ * @return a checksum identifying any change
+ */
+ static long checksum(File file)
+ {
+ CRC32 crc = new CRC32();
+ checksum(file, crc);
+ return crc.getValue();
+ }
+
+ private static void checksum(File file, CRC32 crc)
+ {
+ crc.update(file.getName().getBytes());
+ if (file.isFile())
+ {
+ checksum(file.lastModified(), crc);
+ checksum(file.length(), crc);
+ }
+ else if (file.isDirectory())
+ {
+ File[] children = file.listFiles();
+ if (children != null)
+ {
+ for (int i = 0; i < children.length; i++)
+ {
+ checksum(children[i], crc);
+ }
+ }
+ }
+ }
+
+ private static void checksum(long l, CRC32 crc)
+ {
+ for (int i = 0; i < 8; i++)
+ {
+ crc.update((int) (l & 0x000000ff));
+ l >>= 8;
+ }
+ }
+
+}
Added: felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactInstaller.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactInstaller.java?rev=809470&view=auto
==============================================================================
--- felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactInstaller.java (added)
+++ felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactInstaller.java Mon Aug 31 06:40:43 2009
@@ -0,0 +1,56 @@
+/**
+ *
+ * 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.felix.fileinstall.listener;
+
+import java.io.File;
+
+/**
+ * Objects implementing this interface are able to directly
+ * install and uninstall supported artifacts. Artifacts that
+ * are transformed into bundles should use the
+ * {@link ArtifactTransformer} interface instead.
+ *
+ * Note that fileinstall does not keep track of those artifacts
+ * across restarts, so this means that after a restart, existing
+ * artifacts will be reported as new, while any deleted artifact
+ * won't be reported as deleted.
+ */
+public interface ArtifactInstaller extends ArtifactListener {
+
+ /**
+ * Install the artifact
+ *
+ * @param artifact the artifact to be installed
+ */
+ void install(File artifact) throws Exception;
+
+ /**
+ * Update the artifact
+ *
+ * @param artifact the artifact to be updated
+ */
+ void update(File artifact) throws Exception;
+
+ /**
+ * Uninstall the artifact
+ *
+ * @param artifact the artifact to be uninstalled
+ */
+ void uninstall(File artifact) throws Exception;
+
+}
Added: felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactListener.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactListener.java?rev=809470&view=auto
==============================================================================
--- felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactListener.java (added)
+++ felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactListener.java Mon Aug 31 06:40:43 2009
@@ -0,0 +1,44 @@
+/**
+ *
+ * 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.felix.fileinstall.listener;
+
+import java.io.File;
+
+/**
+ * Interface representing a custom deployment mechanism.
+ *
+ * Classes must implement one of its sub-interface, either
+ * {@link org.apache.felix.fileinstall.listener.ArtifactTransformer} or
+ * {@link org.apache.felix.fileinstall.listener.ArtifactInstaller}.
+ *
+ */
+public interface ArtifactListener {
+
+ /**
+ * Returns true if the listener can process the given artifact.
+ *
+ * Error occuring when checking the artifact should be catched
+ * and not be thrown.
+ *
+ * @param artifact the artifact to check
+ * @return <code>true</code> if this listener supports
+ * the given artifact, <code>false</code> otherwise
+ */
+ boolean canHandle(File artifact);
+
+}
Added: felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactTransformer.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactTransformer.java?rev=809470&view=auto
==============================================================================
--- felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactTransformer.java (added)
+++ felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactTransformer.java Mon Aug 31 06:40:43 2009
@@ -0,0 +1,35 @@
+/**
+ *
+ * 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.felix.fileinstall.listener;
+
+import java.io.File;
+
+/**
+ * Objects implementing this interface are able to convert certain
+ * kind of artifacts to OSGi bundles.
+ *
+ */
+public interface ArtifactTransformer extends ArtifactListener {
+
+ /**
+ * Process the given file (canHandle returned true previously)
+ * Can return <null> or a pointer to a transformed file.
+ */
+ File transform(File artifact, File tmpDir) throws Exception;
+
+}
Modified: felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/util/Util.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/util/Util.java?rev=809470&r1=809469&r2=809470&view=diff
==============================================================================
--- felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/util/Util.java (original)
+++ felix/trunk/fileinstall/src/main/java/org/apache/felix/fileinstall/util/Util.java Mon Aug 31 06:40:43 2009
@@ -19,10 +19,25 @@
package org.apache.felix.fileinstall.util;
import java.io.IOException;
+import java.io.FileInputStream;
+import java.io.File;
+import java.io.BufferedOutputStream;
+import java.io.FileOutputStream;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Map;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.Collections;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import java.util.zip.CRC32;
import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+
+import org.osgi.service.log.LogService;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.BundleContext;
public class Util
{
@@ -30,6 +45,24 @@
private static final String DELIM_STOP = "}";
/**
+ * Perform substitution on a property set
+ *
+ * @param properties the property set to perform substitution on
+ */
+ public static void performSubstitution(Dictionary properties)
+ {
+ for (Enumeration e = properties.keys(); e.hasMoreElements(); )
+ {
+ String name = (String) e.nextElement();
+ Object value = properties.get(name);
+ properties.put(name,
+ value instanceof String
+ ? Util.substVars((String) value, name, null, properties)
+ : value);
+ }
+ }
+
+ /**
* <p>
* This method performs property variable substitution on the
* specified value. If the specified value contains the syntax
@@ -133,35 +166,156 @@
}
/**
- * Check if a file is a legitimate Jar file
- * @param path
- * @return
+ * Log a message and optional throwable. If there is a log service we use
+ * it, otherwise we log to the console
+ *
+ * @param message
+ * The message to log
+ * @param e
+ * The throwable to log
*/
- public static boolean isValidJar(String path)
+ public static void log(BundleContext context, long debug, String message, Throwable e)
{
- JarFile jar = null;
- try
+ LogService log = getLogService(context);
+ if (log == null)
{
- jar = new JarFile(path);
- return true;
- }
- catch (IOException ioe)
- {
- return false;
+ System.out.println(message + (e == null ? "" : ": " + e));
+ if (debug > 0 && e != null)
+ {
+ e.printStackTrace(System.out);
+ }
}
- finally
+ else
{
- if (jar != null)
+ if (e != null)
{
- try
+ log.log(LogService.LOG_ERROR, message, e);
+ if (debug > 0 && e != null)
{
- jar.close();
+ e.printStackTrace();
}
- catch (IOException e)
- {
- //do nothing
+ }
+ else
+ {
+ log.log(LogService.LOG_INFO, message);
+ }
+ }
+ }
+
+ /**
+ * Answer the Log Service
+ *
+ * @return
+ */
+ private static LogService getLogService(BundleContext context)
+ {
+ ServiceReference ref = context.getServiceReference(LogService.class.getName());
+ if (ref != null)
+ {
+ LogService log = (LogService) context.getService(ref);
+ return log;
+ }
+ return null;
+ }
+
+ /**
+ * Jar up a directory
+ *
+ * @param directory
+ * @param zipName
+ * @throws IOException
+ */
+ public static void jarDir(File directory, File zipName) throws IOException {
+ // create a ZipOutputStream to zip the data to
+ JarOutputStream zos = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(zipName)));
+ String path = "";
+ File manFile = new File(directory, JarFile.MANIFEST_NAME);
+ if (manFile.exists()) {
+ byte[] readBuffer = new byte[8192];
+ FileInputStream fis = new FileInputStream(manFile);
+ try {
+ ZipEntry anEntry = new ZipEntry(JarFile.MANIFEST_NAME);
+ zos.putNextEntry(anEntry);
+ int bytesIn = fis.read(readBuffer);
+ while (bytesIn != -1) {
+ zos.write(readBuffer, 0, bytesIn);
+ bytesIn = fis.read(readBuffer);
+ }
+ } finally {
+ fis.close();
+ }
+ zos.closeEntry();
+ }
+ zipDir(directory, zos, path, Collections.singleton(JarFile.MANIFEST_NAME));
+ // close the stream
+ zos.close();
+ }
+
+ /**
+ * Zip up a directory path
+ * @param directory
+ * @param zos
+ * @param path
+ * @param exclusions
+ * @throws IOException
+ */
+ public static void zipDir(File directory, ZipOutputStream zos, String path, Set/* <String> */ exclusions) throws IOException {
+ // get a listing of the directory content
+ File[] dirList = directory.listFiles();
+ byte[] readBuffer = new byte[8192];
+ int bytesIn = 0;
+ // loop through dirList, and zip the files
+ for (int i = 0; i < dirList.length; i++) {
+ File f = dirList[i];
+ if (f.isDirectory()) {
+ zipDir(f, zos, path + f.getName() + "/", exclusions);
+ continue;
+ }
+ String entry = path + f.getName();
+ if (!exclusions.contains(entry)) {
+ FileInputStream fis = new FileInputStream(f);
+ try {
+ ZipEntry anEntry = new ZipEntry(entry);
+ zos.putNextEntry(anEntry);
+ bytesIn = fis.read(readBuffer);
+ while (bytesIn != -1) {
+ zos.write(readBuffer, 0, bytesIn);
+ bytesIn = fis.read(readBuffer);
+ }
+ } finally {
+ fis.close();
}
}
}
}
+
+ /**
+ * Return the latest time at which this file or any child if the file denotes
+ * a directory has been modified
+ *
+ * @param file file or directory to check
+ * @return the latest modification time
+ */
+ public static long getLastModified(File file)
+ {
+ if (file.isFile())
+ {
+ return file.lastModified();
+ }
+ else if (file.isDirectory())
+ {
+ File[] children = file.listFiles();
+ long lastModified = 0;
+ for (int i = 0; i < children.length; i++)
+ {
+ lastModified = Math.max(lastModified, getLastModified(children[i]));
+ }
+ return lastModified;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
}
Modified: felix/trunk/fileinstall/src/main/resources/OSGI-INF/metatype/metatype.xml
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall/src/main/resources/OSGI-INF/metatype/metatype.xml?rev=809470&r1=809469&r2=809470&view=diff
==============================================================================
--- felix/trunk/fileinstall/src/main/resources/OSGI-INF/metatype/metatype.xml (original)
+++ felix/trunk/fileinstall/src/main/resources/OSGI-INF/metatype/metatype.xml Mon Aug 31 06:40:43 2009
@@ -28,6 +28,7 @@
<AD name="Debug" id="felix.fileinstall.debug" required="false" type="String" default="-1"/>
<AD name="Start new bundles?" id="felix.fileinstall.bundles.new.start" required="false" type="String" default="true"/>
<AD name="File name filter" id="felix.fileinstall.filter" required="false" type="String" default=""/>
+ <AD name="Temp directory" id="felix.fileinstall.tmpdir" required="false" type="String" default="tmp"/>
</OCD>
<Designate pid="org.apache.felix.fileinstall">