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
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@809470 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/fileinstall/src/main/java/org/apache/felix/fileinstall/Artifact.java b/fileinstall/src/main/java/org/apache/felix/fileinstall/Artifact.java
new file mode 100644
index 0000000..ab67f78
--- /dev/null
+++ b/fileinstall/src/main/java/org/apache/felix/fileinstall/Artifact.java
@@ -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;
+ }
+}
diff --git a/fileinstall/src/main/java/org/apache/felix/fileinstall/BundleTransformer.java b/fileinstall/src/main/java/org/apache/felix/fileinstall/BundleTransformer.java
new file mode 100644
index 0000000..9cf6511
--- /dev/null
+++ b/fileinstall/src/main/java/org/apache/felix/fileinstall/BundleTransformer.java
@@ -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;
+ }
+
+}
diff --git a/fileinstall/src/main/java/org/apache/felix/fileinstall/ConfigInstaller.java b/fileinstall/src/main/java/org/apache/felix/fileinstall/ConfigInstaller.java
new file mode 100644
index 0000000..ddf637a
--- /dev/null
+++ b/fileinstall/src/main/java/org/apache/felix/fileinstall/ConfigInstaller.java
@@ -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;
+ }
+ }
+
+}
diff --git a/fileinstall/src/main/java/org/apache/felix/fileinstall/DirectoryWatcher.java b/fileinstall/src/main/java/org/apache/felix/fileinstall/DirectoryWatcher.java
index 7f3069c..34f83bb 100644
--- a/fileinstall/src/main/java/org/apache/felix/fileinstall/DirectoryWatcher.java
+++ b/fileinstall/src/main/java/org/apache/felix/fileinstall/DirectoryWatcher.java
@@ -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,69 +61,49 @@
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> */();
- // Represents jars that could not be installed
- Set/* <Bundle> */ startupFailures = new HashSet();
+ // 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 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)
- {
- startBundles = "true".equalsIgnoreCase((String)value);
- }
-
+ 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);
- }
-
- /**
- * Main run loop, will traverse the directory, and then handle the delta
- * between installed and newly found/lost bundles and configurations.
- *
- */
- public void run()
- {
- log("{" + POLL + " (ms) = " + poll + ", "
- + DIR + " = " + watchedDirectory.getAbsolutePath() + ", "
- + DEBUG + " = " + debug + ", "
- + FILTER + " = " + filter + ", "
- + START_NEW_BUNDLES + " = " + startBundles + "}", null);
- initializeCurrentManagedBundles();
- Map currentManagedConfigs = new HashMap(); // location -> Long(time)
FilenameFilter flt;
- if (filter != null)
+ if (filter != null && filter.length() > 0)
{
flt = new FilenameFilter()
{
@@ -132,15 +116,175 @@
{
flt = null;
}
+ scanner = new Scanner(watchedDirectory, flt);
+ }
+
+ /**
+ * Main run loop, will traverse the directory, and then handle the delta
+ * between installed and newly found/lost bundles and configurations.
+ *
+ */
+ public void run()
+ {
+ log("{" + POLL + " (ms) = " + poll + ", "
+ + DIR + " = " + watchedDirectory.getAbsolutePath() + ", "
+ + DEBUG + " = " + debug + ", "
+ + START_NEW_BUNDLES + " = " + startBundles + ", "
+ + TMPDIR + " = " + tmpDir + ", "
+ + FILTER + " = " + filter + "}", null);
+
+ initializeCurrentManagedBundles();
+
+ 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 @@
}
}
}
-
+
+ ArtifactListener findListener(File artifact, List/* <ArtifactListener> */ listeners)
+ {
+ for (Iterator itL = listeners.iterator(); itL.hasNext();)
+ {
+ ArtifactListener listener = (ArtifactListener) itL.next();
+ if (listener.canHandle(artifact))
+ {
+ return listener;
+ }
+ }
+ return null;
+ }
+
+ boolean transformArtifact(Artifact artifact) {
+ if (artifact.getListener() instanceof ArtifactTransformer)
+ {
+ prepareDir(tmpDir);
+ try
+ {
+ File transformed = ((ArtifactTransformer) artifact.getListener()).transform(artifact.getJaredDirectory(), tmpDir);
+ if (transformed != null)
+ {
+ artifact.setTransformed(transformed);
+ return true;
+ }
+ }
+ catch (Exception e)
+ {
+ log("Unable to transform artifact: " + artifact.getPath().getAbsolutePath(), e);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ private void deleteTransformedFile(Artifact artifact) {
+ if (artifact.getTransformed() != null
+ && !artifact.getTransformed().equals(artifact.getPath())
+ && !artifact.getTransformed().delete())
+ {
+ log("Unable to delete transformed artifact: " + artifact.getTransformed().getAbsolutePath(), null);
+ }
+ }
+
+ private void deleteJaredDirectory(Artifact artifact) {
+ if (artifact.getJaredDirectory() != null
+ && !artifact.getJaredDirectory().equals(artifact.getPath())
+ && !artifact.getJaredDirectory().delete())
+ {
+ log("Unable to delete jared artifact: " + artifact.getJaredDirectory().getAbsolutePath(), null);
+ }
+ }
+
/**
* 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
+ *
+ * @param dir
* The directory File Install will monitor
*/
- private void prepareWatchedDir(File watchedDirectory)
+ private void prepareDir(File dir)
{
- if (!watchedDirectory.exists() && !watchedDirectory.mkdirs())
+ if (!dir.exists() && !dir.mkdirs())
{
log("Cannot create folder "
- + watchedDirectory
+ + dir
+ ". Is the folder write-protected?", null);
- throw new RuntimeException("Cannot create folder: " + watchedDirectory);
+ throw new RuntimeException("Cannot create folder: " + dir);
}
- if (!watchedDirectory.isDirectory())
+ if (!dir.isDirectory())
{
- log("Cannot watch "
- + watchedDirectory
+ log("Cannot use "
+ + dir
+ " because it's not a directory", null);
throw new RuntimeException(
- "Cannot start FileInstall to watch something that is not a directory");
- }
- }
-
- /**
- * 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
- {
- // 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(); )
- {
- String path = (String) e.next();
- File f = new File(path);
-
- if (!current.containsKey(path))
- {
- // 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);
- }
- }
- 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();)
- {
- String path = (String) e.next();
- File f = new File(path);
- if (deleteConfig(f))
- {
- current.remove(path);
- }
- }
- }
- catch (Exception ee)
- {
- log("Processing config: ", ee);
- }
- }
-
- /**
- * 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)
- {
- if (debug != 0 && !reported)
- {
- log("Can't find a Configuration Manager, configurations do not work",
- null);
- reported = true;
- }
- return false;
- }
-
- Properties p = new Properties();
- InputStream in = new FileInputStream(f);
- try
- {
- p.load(in);
- }
- 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.
- *
- * @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)
- {
- 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;
- }
- }
-
- 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
- {
- 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);
+ "Cannot start FileInstall using something that is not a directory");
}
}
@@ -444,82 +390,7 @@
*/
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);
- }
- }
- }
-
- /**
- * Answer the Log Service
- *
- * @return
- */
- LogService getLogService()
- {
- ServiceReference ref = context.getServiceReference(LogService.class.getName());
- if (ref != null)
- {
- LogService log = (LogService) context.getService(ref);
- return log;
- }
- return null;
- }
-
- /**
- * 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
- */
- void traverse(Map/* <String, Jar> */ jars, Set configs, File jardir, FilenameFilter filter)
- {
- String list[] = jardir.list(filter);
- if (list == 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);
- }
- }
+ Util.log(context, debug, message, e);
}
/**
@@ -530,23 +401,10 @@
*/
boolean isFragment(Bundle bundle)
{
- PackageAdmin padmin;
- if (FileInstall.padmin == null)
+ PackageAdmin padmin = FileInstall.getPackageAdmin();
+ if (padmin != null)
{
- return false;
- }
-
- try
- {
- padmin = (PackageAdmin) FileInstall.padmin.waitForService(10000);
- if (padmin != null)
- {
- return padmin.getBundleType(bundle) == PackageAdmin.BUNDLE_TYPE_FRAGMENT;
- }
- }
- catch (InterruptedException e)
- {
- // stupid exception
+ return padmin.getBundleType(bundle) == PackageAdmin.BUNDLE_TYPE_FRAGMENT;
}
return false;
}
@@ -556,24 +414,20 @@
*/
void refresh()
{
- PackageAdmin padmin;
- try
+ PackageAdmin padmin = FileInstall.getPackageAdmin();
+ if (padmin != null)
{
- padmin = (PackageAdmin) FileInstall.padmin.waitForService(10000);
padmin.refreshPackages(null);
}
- catch (InterruptedException e)
- {
- Thread.currentThread().interrupt();
- }
}
/**
- * Answer the long from a property.
+ * Retrieve a property as a long.
*
- * @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 long or the default value
*/
long getLong(Dictionary properties, String property, long dflt)
{
@@ -592,6 +446,42 @@
return dflt;
}
+ /**
+ * 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
+ */
+ File getFile(Dictionary properties, String property, File dflt)
+ {
+ String value = (String) properties.get(property);
+ if (value != null)
+ {
+ return new File(value);
+ }
+ return dflt;
+ }
+
+ /**
+ * Retrieve a property as a boolan.
+ *
+ * @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
+ */
+ boolean getBoolean(Dictionary properties, String property, boolean dflt)
+ {
+ String value = (String) properties.get(property);
+ if (value != null)
+ {
+ return Boolean.parseBoolean(value);
+ }
+ return dflt;
+ }
+
public void close()
{
interrupt();
@@ -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())
+ File path = artifact.getPath();
+ // If the listener is an installer, ask for an update
+ if (artifact.getListener() instanceof ArtifactInstaller)
{
- return null; // Don't attempt to install it; nothing has changed.
+ ((ArtifactInstaller) artifact.getListener()).install(path);
}
- File file = new File(path);
- InputStream in = new FileInputStream(file);
- try
+ // else we need to ask for an update on the bundle
+ else if (artifact.getListener() instanceof ArtifactTransformer)
{
- // Some users wanted the location to be a URI (See FELIX-1269)
- final String location = file.toURI().normalize().toString();
- bundle = context.installBundle(location, in);
+ 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());
}
- finally
- {
- in.close();
- }
+ 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)
+ 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)
{
- log("Failed to uninstall bundle: "
- + jar.getPath() + " with id: "
- + old.getBundleId()
- + ". The bundle has already been uninstalled", null);
- return null;
+ ((ArtifactInstaller) artifact.getListener()).uninstall(path);
}
- bundle.uninstall();
+ // 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();
+ }
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)
+ File path = artifact.getPath();
+ // If the listener is an installer, ask for an update
+ if (artifact.getListener() instanceof ArtifactInstaller)
{
- log("Failed to update bundle: "
- + jar.getPath() + " with ID "
- + jar.getBundleId()
- + ". The bundle has been uninstalled", null);
- return null;
+ ((ArtifactInstaller) artifact.getListener()).update(path);
}
- bundle.update(in);
- startupFailures.remove(bundle);
- jar.setLastModified(bundle.getLastModified());
- log("Updated " + jar.getPath(), null);
- return bundle;
- }
- catch (Exception e)
- {
- log("Failed to update bundle " + jar.getPath(), e);
- }
- finally
- {
- if (in != null)
+ // 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();
}
- catch (IOException e)
- {
- }
}
+ startupFailures.remove(bundle);
+ artifact.setLastModified(Util.getLastModified(path));
+ log("Updated " + path, null);
}
- return null;
+ catch (Exception e)
+ {
+ log("Failed to update artifact " + artifact.getPath(), e);
+ }
+ return bundle;
+ }
+
+ private void start(Collection/* <Bundle> */ bundles)
+ {
+ for (Iterator b = bundles.iterator(); b.hasNext(); )
+ {
+ start((Bundle) b.next());
+ }
}
private void start(Bundle bundle)
diff --git a/fileinstall/src/main/java/org/apache/felix/fileinstall/FileInstall.java b/fileinstall/src/main/java/org/apache/felix/fileinstall/FileInstall.java
index da31be4..6238f64 100644
--- a/fileinstall/src/main/java/org/apache/felix/fileinstall/FileInstall.java
+++ b/fileinstall/src/main/java/org/apache/felix/fileinstall/FileInstall.java
@@ -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
diff --git a/fileinstall/src/main/java/org/apache/felix/fileinstall/Scanner.java b/fileinstall/src/main/java/org/apache/felix/fileinstall/Scanner.java
new file mode 100644
index 0000000..0be8deb
--- /dev/null
+++ b/fileinstall/src/main/java/org/apache/felix/fileinstall/Scanner.java
@@ -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;
+ }
+ }
+
+}
diff --git a/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactInstaller.java b/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactInstaller.java
new file mode 100644
index 0000000..0a9de7d
--- /dev/null
+++ b/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactInstaller.java
@@ -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;
+
+}
diff --git a/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactListener.java b/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactListener.java
new file mode 100644
index 0000000..17b9e1a
--- /dev/null
+++ b/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactListener.java
@@ -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);
+
+}
diff --git a/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactTransformer.java b/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactTransformer.java
new file mode 100644
index 0000000..a056fdc
--- /dev/null
+++ b/fileinstall/src/main/java/org/apache/felix/fileinstall/listener/ArtifactTransformer.java
@@ -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;
+
+}
diff --git a/fileinstall/src/main/java/org/apache/felix/fileinstall/util/Util.java b/fileinstall/src/main/java/org/apache/felix/fileinstall/util/Util.java
index a81f376..0a8c651 100644
--- a/fileinstall/src/main/java/org/apache/felix/fileinstall/util/Util.java
+++ b/fileinstall/src/main/java/org/apache/felix/fileinstall/util/Util.java
@@ -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
+ * 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 void log(BundleContext context, long debug, String message, Throwable e)
+ {
+ LogService log = getLogService(context);
+ 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);
+ }
+ }
+ }
+
+ /**
+ * Answer the Log Service
+ *
* @return
*/
- public static boolean isValidJar(String path)
+ private static LogService getLogService(BundleContext context)
{
- JarFile jar = null;
- try
+ ServiceReference ref = context.getServiceReference(LogService.class.getName());
+ if (ref != null)
{
- jar = new JarFile(path);
- return true;
+ LogService log = (LogService) context.getService(ref);
+ return log;
}
- catch (IOException ioe)
- {
- return false;
- }
- finally
- {
- if (jar != null)
- {
- try
- {
- jar.close();
+ 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);
}
- catch (IOException e)
- {
- //do nothing
+ } 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;
+ }
+ }
+
}
diff --git a/fileinstall/src/main/resources/OSGI-INF/metatype/metatype.xml b/fileinstall/src/main/resources/OSGI-INF/metatype/metatype.xml
index 6cf2117..1acfd0f 100644
--- a/fileinstall/src/main/resources/OSGI-INF/metatype/metatype.xml
+++ b/fileinstall/src/main/resources/OSGI-INF/metatype/metatype.xml
@@ -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">
diff --git a/fileinstall/src/test/java/org/apache/felix/fileinstall/BundleTransformerTest.java b/fileinstall/src/test/java/org/apache/felix/fileinstall/BundleTransformerTest.java
new file mode 100644
index 0000000..8f64573
--- /dev/null
+++ b/fileinstall/src/test/java/org/apache/felix/fileinstall/BundleTransformerTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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 junit.framework.TestCase;
+
+/**
+ * Test for the BundleTransformer
+ */
+public class BundleTransformerTest extends TestCase
+{
+
+ public void testCanRecognizeInvalidJar()
+ {
+ assertFalse(new BundleTransformer().canHandle(new File("src/test/resources/watched/firstjar.jar")));
+ assertFalse(new BundleTransformer().canHandle(new File("src/test/resources/watched/notexistentfile.jar")));
+ }
+
+}
diff --git a/fileinstall/src/test/java/org/apache/felix/fileinstall/ConfigInstallerTest.java b/fileinstall/src/test/java/org/apache/felix/fileinstall/ConfigInstallerTest.java
new file mode 100644
index 0000000..c1d8ce7
--- /dev/null
+++ b/fileinstall/src/test/java/org/apache/felix/fileinstall/ConfigInstallerTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.util.Hashtable;
+import java.util.Dictionary;
+
+import junit.framework.TestCase;
+import org.easymock.MockControl;
+import org.easymock.ArgumentsMatcher;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Bundle;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.Configuration;
+
+/**
+ * Tests for ConfigInstaller
+ */
+public class ConfigInstallerTest extends TestCase {
+
+ MockControl mockBundleContextControl;
+ BundleContext mockBundleContext;
+ MockControl mockBundleControl;
+ Bundle mockBundle;
+ MockControl mockConfigurationAdminControl;
+ ConfigurationAdmin mockConfigurationAdmin;
+ MockControl mockConfigurationControl;
+ Configuration mockConfiguration;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ mockBundleContextControl = MockControl.createControl( BundleContext.class );
+ mockBundleContext = ( BundleContext ) mockBundleContextControl.getMock();
+ mockBundleControl = MockControl.createControl( Bundle.class );
+ mockBundle = ( Bundle ) mockBundleControl.getMock();
+ mockConfigurationAdminControl = MockControl.createControl( ConfigurationAdmin.class );
+ mockConfigurationAdmin = ( ConfigurationAdmin ) mockConfigurationAdminControl.getMock();
+ mockConfigurationControl = MockControl.createControl( Configuration.class );
+ mockConfiguration = ( Configuration ) mockConfigurationControl.getMock();
+ }
+
+
+ public void testParsePidWithoutFactoryPid()
+ {
+ mockBundleContextControl.replay();
+ ConfigInstaller ci = new ConfigInstaller(null);
+
+ String path = "pid.cfg";
+ assertEquals( "Pid without Factory Pid calculated", "pid", ci.parsePid( path )[0] );
+ assertEquals( "Pid without Factory Pid calculated", null, ci.parsePid( path )[1] );
+ }
+
+
+ public void testParsePidWithFactoryPid()
+ {
+ mockBundleContextControl.replay();
+ ConfigInstaller ci = new ConfigInstaller(null);
+
+ String path = "factory-pid.cfg";
+ assertEquals( "Pid with Factory Pid calculated", "factory", ci.parsePid( path )[0] );
+ assertEquals( "Pid with Factory Pid calculated", "pid", ci.parsePid( path )[1] );
+ }
+
+ public void testGetNewFactoryConfiguration() throws Exception
+ {
+ mockConfigurationControl.replay();
+ mockConfigurationAdmin.listConfigurations( null );
+ mockConfigurationAdminControl.setMatcher( MockControl.ALWAYS_MATCHER );
+ mockConfigurationAdminControl.setReturnValue( null );
+ mockConfigurationAdmin.createFactoryConfiguration( "pid", null );
+ mockConfigurationAdminControl.setReturnValue( mockConfiguration );
+ mockConfigurationAdminControl.replay();
+ mockBundleContext.createFilter( "" );
+ mockBundleContextControl.setMatcher( MockControl.ALWAYS_MATCHER );
+ mockBundleContextControl.setReturnValue( null );
+ mockBundleContextControl.replay();
+
+ FileInstall.cmTracker = new MockServiceTracker( mockBundleContext, mockConfigurationAdmin );
+ ConfigInstaller ci = new ConfigInstaller( mockBundleContext );
+
+ assertEquals( "Factory configuration retrieved", mockConfiguration, ci.getConfiguration( "pid", "factoryPid" ) );
+
+ mockConfigurationAdminControl.verify();
+ mockConfigurationControl.verify();
+ mockBundleContextControl.verify();
+ }
+
+
+ public void testGetExistentFactoryConfiguration() throws Exception
+ {
+ mockConfigurationControl.replay();
+ mockConfigurationAdmin.listConfigurations( null );
+ mockConfigurationAdminControl.setMatcher( MockControl.ALWAYS_MATCHER );
+ mockConfigurationAdminControl.setReturnValue( null );
+ mockConfigurationAdmin.createFactoryConfiguration( "pid", null );
+ mockConfigurationAdminControl.setReturnValue( mockConfiguration );
+ mockConfigurationAdminControl.replay();
+ mockBundleContext.createFilter( "" );
+ mockBundleContextControl.setMatcher( MockControl.ALWAYS_MATCHER );
+ mockBundleContextControl.setReturnValue( null );
+ mockBundleContextControl.replay();
+
+ FileInstall.cmTracker = new MockServiceTracker( mockBundleContext, mockConfigurationAdmin );
+ ConfigInstaller ci = new ConfigInstaller( mockBundleContext );
+
+ assertEquals( "Factory configuration retrieved", mockConfiguration, ci.getConfiguration( "pid", "factoryPid" ) );
+
+ mockConfigurationAdminControl.verify();
+ mockConfigurationControl.verify();
+ mockBundleContextControl.verify();
+ }
+
+
+ public void testGetExistentNoFactoryConfiguration() throws Exception
+ {
+ mockConfigurationControl.replay();
+ mockConfigurationAdmin.listConfigurations( null );
+ mockConfigurationAdminControl.setMatcher( MockControl.ALWAYS_MATCHER );
+ mockConfigurationAdminControl.setReturnValue( null );
+ mockConfigurationAdmin.getConfiguration( "pid", null );
+ mockConfigurationAdminControl.setReturnValue( mockConfiguration );
+ mockConfigurationAdminControl.replay();
+ mockBundleContext.createFilter( "" );
+ mockBundleContextControl.setMatcher( MockControl.ALWAYS_MATCHER );
+ mockBundleContextControl.setReturnValue( null );
+ mockBundleContextControl.replay();
+
+ FileInstall.cmTracker = new MockServiceTracker( mockBundleContext, mockConfigurationAdmin );
+ ConfigInstaller ci = new ConfigInstaller( mockBundleContext );
+
+ assertEquals( "Factory configuration retrieved", mockConfiguration, ci.getConfiguration( "pid", null ) );
+
+ mockConfigurationAdminControl.verify();
+ mockConfigurationControl.verify();
+ mockBundleContextControl.verify();
+ }
+
+
+ public void testDeleteConfig() throws Exception
+ {
+ mockConfiguration.delete();
+ mockConfigurationControl.replay();
+ mockConfigurationAdmin.listConfigurations( null );
+ mockConfigurationAdminControl.setMatcher( MockControl.ALWAYS_MATCHER );
+ mockConfigurationAdminControl.setReturnValue( null );
+ mockConfigurationAdmin.getConfiguration( "pid", null );
+ mockConfigurationAdminControl.setReturnValue( mockConfiguration );
+ mockConfigurationAdminControl.replay();
+ mockBundleContext.createFilter( "" );
+ mockBundleContextControl.setMatcher( MockControl.ALWAYS_MATCHER );
+ mockBundleContextControl.setReturnValue( null );
+ mockBundleContextControl.replay();
+
+ FileInstall.cmTracker = new MockServiceTracker( mockBundleContext, mockConfigurationAdmin );
+ ConfigInstaller ci = new ConfigInstaller( mockBundleContext );
+
+ assertTrue( ci.deleteConfig( new File( "pid.cfg" ) ) );
+
+ mockConfigurationAdminControl.verify();
+ mockConfigurationControl.verify();
+ mockBundleContextControl.verify();
+ }
+
+
+ public void testSetConfiguration() throws Exception
+ {
+ mockConfiguration.getBundleLocation();
+ mockConfigurationControl.setReturnValue( null );
+ mockConfiguration.update( new Hashtable() );
+ mockConfigurationControl.setMatcher( new ArgumentsMatcher()
+ {
+ public boolean matches( Object[] expected, Object[] actual )
+ {
+ return ( actual.length == 1 ) && ( (Dictionary) actual[0] ).get( "testkey" ).equals( "testvalue" );
+ }
+
+
+ public String toString( Object[] arg0 )
+ {
+ return arg0.toString();
+ }
+ } );
+ mockConfigurationControl.replay();
+ mockConfigurationAdmin.listConfigurations( null );
+ mockConfigurationAdminControl.setMatcher( MockControl.ALWAYS_MATCHER );
+ mockConfigurationAdminControl.setReturnValue( null );
+ mockConfigurationAdmin.getConfiguration( "firstcfg", null );
+ mockConfigurationAdminControl.setReturnValue( mockConfiguration );
+ mockConfigurationAdminControl.replay();
+ mockBundleContext.createFilter( "" );
+ mockBundleContextControl.setMatcher( MockControl.ALWAYS_MATCHER );
+ mockBundleContextControl.setReturnValue( null );
+ mockBundleContextControl.replay();
+
+ FileInstall.cmTracker = new MockServiceTracker( mockBundleContext, mockConfigurationAdmin );
+ ConfigInstaller ci = new ConfigInstaller( mockBundleContext );
+
+ assertTrue( ci.setConfig( new File( "src/test/resources/watched/firstcfg.cfg" ) ) );
+
+ mockConfigurationAdminControl.verify();
+ mockConfigurationControl.verify();
+ mockBundleContextControl.verify();
+ }
+
+
+}
diff --git a/fileinstall/src/test/java/org/apache/felix/fileinstall/DirectoryWatcherTest.java b/fileinstall/src/test/java/org/apache/felix/fileinstall/DirectoryWatcherTest.java
index 0326ef5..3dd9116 100644
--- a/fileinstall/src/test/java/org/apache/felix/fileinstall/DirectoryWatcherTest.java
+++ b/fileinstall/src/test/java/org/apache/felix/fileinstall/DirectoryWatcherTest.java
@@ -21,18 +21,13 @@
import java.io.File;
import java.util.Dictionary;
-import java.util.HashSet;
import java.util.Hashtable;
-import java.util.Set;
import junit.framework.TestCase;
-import org.easymock.ArgumentsMatcher;
import org.easymock.MockControl;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
-import org.osgi.service.cm.Configuration;
-import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.packageadmin.PackageAdmin;
@@ -51,10 +46,6 @@
PackageAdmin mockPackageAdmin;
MockControl mockBundleControl;
Bundle mockBundle;
- MockControl mockConfigurationAdminControl;
- ConfigurationAdmin mockConfigurationAdmin;
- MockControl mockConfigurationControl;
- Configuration mockConfiguration;
protected void setUp() throws Exception
@@ -66,10 +57,6 @@
mockPackageAdmin = ( PackageAdmin ) mockPackageAdminControl.getMock();
mockBundleControl = MockControl.createControl( Bundle.class );
mockBundle = ( Bundle ) mockBundleControl.getMock();
- mockConfigurationAdminControl = MockControl.createControl( ConfigurationAdmin.class );
- mockConfigurationAdmin = ( ConfigurationAdmin ) mockConfigurationAdminControl.getMock();
- mockConfigurationControl = MockControl.createControl( Configuration.class );
- mockConfiguration = ( Configuration ) mockConfigurationControl.getMock();
}
@@ -102,12 +89,60 @@
}
+ public void testGetBooleanWithNonExistentProperty()
+ {
+ mockBundleContextControl.replay();
+ dw = new DirectoryWatcher( props, mockBundleContext );
+ assertEquals( "getBoolean gives the default value for non-existing properties", true, dw.getBoolean( props, TEST, true ) );
+ }
+
+
+ public void testGetBooleanWithExistentProperty()
+ {
+ props.put( TEST, "true" );
+ mockBundleContextControl.replay();
+ dw = new DirectoryWatcher( props, mockBundleContext );
+ assertEquals( "getBoolean retrieves the right property value", true, dw.getBoolean( props, TEST, false ) );
+ }
+
+
+ public void testGetBooleanWithIncorrectValue()
+ {
+ props.put( TEST, "incorrect" );
+
+ mockBundleContext.getServiceReference( "org.osgi.service.log.LogService" );
+ mockBundleContextControl.setReturnValue( null );
+ mockBundleContextControl.replay();
+ dw = new DirectoryWatcher( props, mockBundleContext );
+ assertEquals( "getBoolean retrieves the right property value", false, dw.getBoolean( props, TEST, true ) );
+ }
+
+
+ public void testGetFileWithNonExistentProperty()
+ {
+ mockBundleContextControl.replay();
+ dw = new DirectoryWatcher( props, mockBundleContext );
+ assertEquals( "getFile gives the default value for non-existing properties", new File("tmp"), dw.getFile( props, TEST, new File("tmp") ) );
+ }
+
+
+ public void testGetFileWithExistentProperty()
+ {
+ props.put( TEST, "test" );
+ mockBundleContextControl.replay();
+ dw = new DirectoryWatcher( props, mockBundleContext );
+ assertEquals( "getBoolean retrieves the right property value", new File("test"), dw.getFile( props, TEST, new File("tmp") ) );
+ }
+
+
public void testParameterAfterInitialization()
{
props.put( DirectoryWatcher.POLL, "500" );
props.put( DirectoryWatcher.DEBUG, "1" );
props.put( DirectoryWatcher.START_NEW_BUNDLES, "false" );
props.put( DirectoryWatcher.DIR, new File( "src/test/resources" ).getAbsolutePath() );
+ props.put( DirectoryWatcher.TMPDIR, new File( "src/test/resources" ).getAbsolutePath() );
+ props.put( DirectoryWatcher.FILTER, ".*\\.cfg" );
mockBundleContextControl.replay();
dw = new DirectoryWatcher( props, mockBundleContext );
@@ -115,7 +150,10 @@
assertEquals( "DEBUG parameter correctly read", 1l, dw.debug );
assertTrue( "DIR parameter correctly read", dw.watchedDirectory.getAbsolutePath().endsWith(
"src" + File.separatorChar + "test" + File.separatorChar + "resources" ) );
+ assertTrue( "TMPDIR parameter correctly read", dw.tmpDir.getAbsolutePath().endsWith(
+ "src" + File.separatorChar + "test" + File.separatorChar + "resources" ) );
assertEquals( "START_NEW_BUNDLES parameter correctly read", false, dw.startBundles );
+ assertEquals( "FILTER parameter correctly read", ".*\\.cfg", dw.filter );
}
@@ -125,30 +163,14 @@
mockBundleContextControl.replay();
dw = new DirectoryWatcher( props, mockBundleContext );
+ assertTrue( "DIR parameter correctly read", dw.watchedDirectory.getAbsolutePath().endsWith(
+ "src" + File.separatorChar + "test" + File.separatorChar + "resources" ) );
assertEquals( "Default POLL parameter correctly read", 2000l, dw.poll );
assertEquals( "Default DEBUG parameter correctly read", -1l, dw.debug );
+ assertTrue( "Default TMPDIR parameter correctly read", dw.tmpDir.getAbsolutePath().endsWith(
+ File.separatorChar + "tmp" ) );
assertEquals( "Default START_NEW_BUNDLES parameter correctly read", true, dw.startBundles );
- }
-
-
- public void testParsePidWithoutFactoryPid()
- {
- mockBundleContextControl.replay();
- dw = new DirectoryWatcher( props, mockBundleContext );
- String path = "pid.cfg";
- assertEquals( "Pid without Factory Pid calculated", "pid", dw.parsePid( path )[0] );
- assertEquals( "Pid without Factory Pid calculated", null, dw.parsePid( path )[1] );
- }
-
-
- public void testParsePidWithFactoryPid()
- {
- mockBundleContextControl.replay();
- dw = new DirectoryWatcher( props, mockBundleContext );
-
- String path = "factory-pid.cfg";
- assertEquals( "Pid with Factory Pid calculated", "factory", dw.parsePid( path )[0] );
- assertEquals( "Pid with Factory Pid calculated", "pid", dw.parsePid( path )[1] );
+ assertEquals( "Default FILTER parameter correctly read", null, dw.filter );
}
@@ -173,145 +195,4 @@
}
- public void testGetNewFactoryConfiguration() throws Exception
- {
- mockConfigurationControl.replay();
- mockConfigurationAdmin.listConfigurations( null );
- mockConfigurationAdminControl.setMatcher( MockControl.ALWAYS_MATCHER );
- mockConfigurationAdminControl.setReturnValue( null );
- mockConfigurationAdmin.createFactoryConfiguration( "pid", null );
- mockConfigurationAdminControl.setReturnValue( mockConfiguration );
- mockConfigurationAdminControl.replay();
- mockBundleContext.createFilter( "" );
- mockBundleContextControl.setMatcher( MockControl.ALWAYS_MATCHER );
- mockBundleContextControl.setReturnValue( null );
- mockBundleContextControl.replay();
-
- FileInstall.cmTracker = new MockServiceTracker( mockBundleContext, mockConfigurationAdmin );
- dw = new DirectoryWatcher( props, mockBundleContext );
-
- assertEquals( "Factory configuration retrieved", mockConfiguration, dw.getConfiguration( "pid", "factoryPid" ) );
-
- mockConfigurationAdminControl.verify();
- mockConfigurationControl.verify();
- mockBundleContextControl.verify();
- }
-
-
- public void testGetExistentFactoryConfiguration() throws Exception
- {
- mockConfigurationControl.replay();
- mockConfigurationAdmin.listConfigurations( null );
- mockConfigurationAdminControl.setMatcher( MockControl.ALWAYS_MATCHER );
- mockConfigurationAdminControl.setReturnValue( null );
- mockConfigurationAdmin.createFactoryConfiguration( "pid", null );
- mockConfigurationAdminControl.setReturnValue( mockConfiguration );
- mockConfigurationAdminControl.replay();
- mockBundleContext.createFilter( "" );
- mockBundleContextControl.setMatcher( MockControl.ALWAYS_MATCHER );
- mockBundleContextControl.setReturnValue( null );
- mockBundleContextControl.replay();
-
- FileInstall.cmTracker = new MockServiceTracker( mockBundleContext, mockConfigurationAdmin );
- dw = new DirectoryWatcher( props, mockBundleContext );
-
- assertEquals( "Factory configuration retrieved", mockConfiguration, dw.getConfiguration( "pid", "factoryPid" ) );
-
- mockConfigurationAdminControl.verify();
- mockConfigurationControl.verify();
- mockBundleContextControl.verify();
- }
-
-
- public void testGetExistentNoFactoryConfiguration() throws Exception
- {
- mockConfigurationControl.replay();
- mockConfigurationAdmin.listConfigurations( null );
- mockConfigurationAdminControl.setMatcher( MockControl.ALWAYS_MATCHER );
- mockConfigurationAdminControl.setReturnValue( null );
- mockConfigurationAdmin.getConfiguration( "pid", null );
- mockConfigurationAdminControl.setReturnValue( mockConfiguration );
- mockConfigurationAdminControl.replay();
- mockBundleContext.createFilter( "" );
- mockBundleContextControl.setMatcher( MockControl.ALWAYS_MATCHER );
- mockBundleContextControl.setReturnValue( null );
- mockBundleContextControl.replay();
-
- FileInstall.cmTracker = new MockServiceTracker( mockBundleContext, mockConfigurationAdmin );
- dw = new DirectoryWatcher( props, mockBundleContext );
-
- assertEquals( "Factory configuration retrieved", mockConfiguration, dw.getConfiguration( "pid", null ) );
-
- mockConfigurationAdminControl.verify();
- mockConfigurationControl.verify();
- mockBundleContextControl.verify();
- }
-
-
- public void testDeleteConfig() throws Exception
- {
- mockConfiguration.delete();
- mockConfigurationControl.replay();
- mockConfigurationAdmin.listConfigurations( null );
- mockConfigurationAdminControl.setMatcher( MockControl.ALWAYS_MATCHER );
- mockConfigurationAdminControl.setReturnValue( null );
- mockConfigurationAdmin.getConfiguration( "pid", null );
- mockConfigurationAdminControl.setReturnValue( mockConfiguration );
- mockConfigurationAdminControl.replay();
- mockBundleContext.createFilter( "" );
- mockBundleContextControl.setMatcher( MockControl.ALWAYS_MATCHER );
- mockBundleContextControl.setReturnValue( null );
- mockBundleContextControl.replay();
-
- FileInstall.cmTracker = new MockServiceTracker( mockBundleContext, mockConfigurationAdmin );
- dw = new DirectoryWatcher( props, mockBundleContext );
-
- assertTrue( dw.deleteConfig( new File( "pid.cfg" ) ) );
-
- mockConfigurationAdminControl.verify();
- mockConfigurationControl.verify();
- mockBundleContextControl.verify();
- }
-
-
- public void testSetConfiguration() throws Exception
- {
- mockConfiguration.getBundleLocation();
- mockConfigurationControl.setReturnValue( null );
- mockConfiguration.update( new Hashtable() );
- mockConfigurationControl.setMatcher( new ArgumentsMatcher()
- {
- public boolean matches( Object[] expected, Object[] actual )
- {
- return ( actual.length == 1 ) && ( ( Dictionary ) actual[0] ).get( "testkey" ).equals( "testvalue" );
- }
-
-
- public String toString( Object[] arg0 )
- {
- return arg0.toString();
- }
- } );
- mockConfigurationControl.replay();
- mockConfigurationAdmin.listConfigurations( null );
- mockConfigurationAdminControl.setMatcher( MockControl.ALWAYS_MATCHER );
- mockConfigurationAdminControl.setReturnValue( null );
- mockConfigurationAdmin.getConfiguration( "firstcfg", null );
- mockConfigurationAdminControl.setReturnValue( mockConfiguration );
- mockConfigurationAdminControl.replay();
- mockBundleContext.createFilter( "" );
- mockBundleContextControl.setMatcher( MockControl.ALWAYS_MATCHER );
- mockBundleContextControl.setReturnValue( null );
- mockBundleContextControl.replay();
-
- FileInstall.cmTracker = new MockServiceTracker( mockBundleContext, mockConfigurationAdmin );
- dw = new DirectoryWatcher( props, mockBundleContext );
-
- assertTrue( dw.setConfig( new File( "src/test/resources/watched/firstcfg.cfg" ) ) );
-
- mockConfigurationAdminControl.verify();
- mockConfigurationControl.verify();
- mockBundleContextControl.verify();
- }
-
}
diff --git a/fileinstall/src/test/java/org/apache/felix/fileinstall/util/UtilTest.java b/fileinstall/src/test/java/org/apache/felix/fileinstall/util/UtilTest.java
index ca68a37..291349e 100644
--- a/fileinstall/src/test/java/org/apache/felix/fileinstall/util/UtilTest.java
+++ b/fileinstall/src/test/java/org/apache/felix/fileinstall/util/UtilTest.java
@@ -53,9 +53,4 @@
assertEquals("${a", Util.substVars("${a", "b", null, new Hashtable()));
}
- public void testCanRecognizeInvalidJar()
- {
- assertFalse(Util.isValidJar("src/test/resources/watched/firstjar.jar"));
- assertFalse(Util.isValidJar("src/test/resources/watched/notexistentfile.jar"));
- }
}
\ No newline at end of file