[FELIX-2571] Have fileinstall listen for configuration changes and write them back to the config files
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1027254 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/fileinstall/pom.xml b/fileinstall/pom.xml
index 9ba034a..b5b54c9 100644
--- a/fileinstall/pom.xml
+++ b/fileinstall/pom.xml
@@ -45,7 +45,13 @@
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.configadmin</artifactId>
- <version>1.2.4</version>
+ <version>1.2.8</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.utils</artifactId>
+ <version>1.0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
@@ -54,7 +60,7 @@
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
- <version>2.0.0</version>
+ <version>2.1.0</version>
<extensions>true</extensions>
<configuration>
<instructions>
@@ -62,20 +68,27 @@
org.apache.felix.fileinstall;version=${project.version}
</Export-Package>
<Private-Package>
- org.apache.felix.fileinstall.internal
+ org.apache.felix.fileinstall.internal,
+ org.apache.felix.utils.properties,
</Private-Package>
<Import-Package>
org.osgi.service.log;resolution:=optional,
org.osgi.service.cm;resolution:=optional,
*
</Import-Package>
- <Include-Resource>src/main/resources,META-INF/LICENSE=LICENSE,META-INF/NOTICE=NOTICE,META-INF/DEPENDENCIES=DEPENDENCIES</Include-Resource>
+ <Include-Resource>
+ src/main/resources,
+ META-INF/LICENSE=LICENSE,
+ META-INF/NOTICE=NOTICE,
+ META-INF/DEPENDENCIES=DEPENDENCIES
+ </Include-Resource>
<Bundle-Activator>org.apache.felix.fileinstall.internal.FileInstall</Bundle-Activator>
<Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
<Bundle-Vendor>The Apache Software Foundation</Bundle-Vendor>
<_versionpolicy>[$(version;==;$(@)),$(version;+;$(@)))</_versionpolicy>
<Embed-Dependency>
- org.apache.felix.configadmin;inline="org/apache/felix/cm/file/ConfigurationHandler.*"
+ org.apache.felix.configadmin;inline="org/apache/felix/cm/file/ConfigurationHandler.*",
+ org.apache.felix.utils;inline="org/apache/felix/utils/collections/DictionaryAsMap*.*"
</Embed-Dependency>
</instructions>
</configuration>
diff --git a/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/ConfigInstaller.java b/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/ConfigInstaller.java
index 73e45c8..e386d6f 100644
--- a/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/ConfigInstaller.java
+++ b/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/ConfigInstaller.java
@@ -18,24 +18,39 @@
*/
package org.apache.felix.fileinstall.internal;
-import java.io.*;
-import java.util.*;
-
import org.apache.felix.cm.file.ConfigurationHandler;
import org.apache.felix.fileinstall.ArtifactInstaller;
import org.apache.felix.fileinstall.internal.Util.Logger;
+import org.apache.felix.utils.properties.InterpolationHelper;
import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationEvent;
+import org.osgi.service.cm.ConfigurationListener;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Properties;
/**
* ArtifactInstaller for configurations.
* TODO: This service lifecycle should be bound to the ConfigurationAdmin service lifecycle.
*/
-public class ConfigInstaller implements ArtifactInstaller
+public class ConfigInstaller implements ArtifactInstaller, ConfigurationListener
{
private final BundleContext context;
private final ConfigurationAdmin configAdmin;
+ private ServiceRegistration registration;
ConfigInstaller(BundleContext context, ConfigurationAdmin configAdmin)
{
@@ -43,6 +58,24 @@
this.configAdmin = configAdmin;
}
+ public void init()
+ {
+ if (registration == null)
+ {
+ Properties props = new Properties();
+ registration = this.context.registerService(ConfigurationListener.class.getName(), this, props);
+ }
+ }
+
+ public void destroy()
+ {
+ if (registration != null)
+ {
+ registration.unregister();
+ registration = null;
+ }
+ }
+
public boolean canHandle(File artifact)
{
return artifact.getName().endsWith(".cfg")
@@ -64,6 +97,54 @@
deleteConfig(artifact);
}
+ public void configurationEvent(ConfigurationEvent configurationEvent)
+ {
+ if (configurationEvent.getType() == ConfigurationEvent.CM_UPDATED)
+ {
+ try
+ {
+ Configuration config = getConfigurationAdmin().getConfiguration(
+ configurationEvent.getPid(),
+ configurationEvent.getFactoryPid());
+ Dictionary dict = config.getProperties();
+ String fileName = (String) dict.get( DirectoryWatcher.FILENAME );
+ File file = fileName != null ? new File(fileName) : null;
+ if( file != null && file.isFile() ) {
+ if( fileName.endsWith( ".cfg" ) )
+ {
+ org.apache.felix.utils.properties.Properties props = new org.apache.felix.utils.properties.Properties( file );
+ for( Enumeration e = dict.keys(); e.hasMoreElements(); )
+ {
+ String key = e.nextElement().toString();
+ if( !Constants.SERVICE_PID.equals(key) && !DirectoryWatcher.FILENAME.equals(key) )
+ {
+ String val = dict.get( key ).toString();
+ props.put( key, val );
+ }
+ }
+ props.save();
+ }
+ else if( fileName.endsWith( ".config" ) )
+ {
+ OutputStream fos = new FileOutputStream( file );
+ try
+ {
+ ConfigurationHandler.write( fos, dict );
+ }
+ finally
+ {
+ fos.close();
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Util.log( context, Util.getGlobalLogLevel(context), Logger.LOG_INFO, "Unable to save configuration", e );
+ }
+ }
+ }
+
ConfigurationAdmin getConfigurationAdmin()
{
return configAdmin;
@@ -83,7 +164,7 @@
final InputStream in = new BufferedInputStream(new FileInputStream(f));
try
{
- if ( f.getName().endsWith(".cfg") )
+ if ( f.getName().endsWith( ".cfg" ) )
{
final Properties p = new Properties();
in.mark(1);
@@ -94,10 +175,10 @@
} else {
p.load(in);
}
- Util.performSubstitution(p);
+ InterpolationHelper.performSubstitution((Map) p);
ht.putAll(p);
}
- else
+ else if ( f.getName().endsWith( ".config" ) )
{
final Dictionary config = ConfigurationHandler.read(in);
final Enumeration i = config.keys();
@@ -114,8 +195,8 @@
}
String pid[] = parsePid(f.getName());
- ht.put(DirectoryWatcher.FILENAME, f.getName());
- Configuration config = getConfiguration(pid[0], pid[1]);
+ ht.put(DirectoryWatcher.FILENAME, f.getAbsolutePath());
+ Configuration config = getConfiguration(f.getAbsolutePath(), pid[0], pid[1]);
if (config.getBundleLocation() != null)
{
config.setBundleLocation(null);
@@ -135,7 +216,7 @@
boolean deleteConfig(File f) throws Exception
{
String pid[] = parsePid(f.getName());
- Configuration config = getConfiguration(pid[0], pid[1]);
+ Configuration config = getConfiguration(f.getAbsolutePath(), pid[0], pid[1]);
config.delete();
return true;
}
@@ -162,10 +243,10 @@
}
}
- Configuration getConfiguration(String pid, String factoryPid)
+ Configuration getConfiguration(String fileName, String pid, String factoryPid)
throws Exception
{
- Configuration oldConfiguration = findExistingConfiguration(pid, factoryPid);
+ Configuration oldConfiguration = findExistingConfiguration(fileName);
if (oldConfiguration != null)
{
Util.log(context, Util.getGlobalLogLevel(context),
@@ -188,11 +269,9 @@
}
}
- Configuration findExistingConfiguration(String pid, String factoryPid) throws Exception
+ Configuration findExistingConfiguration(String fileName) throws Exception
{
- String suffix = factoryPid == null ? ".cfg" : "-" + factoryPid + ".cfg";
-
- String filter = "(" + DirectoryWatcher.FILENAME + "=" + pid + suffix + ")";
+ String filter = "(" + DirectoryWatcher.FILENAME + "=" + fileName + ")";
Configuration[] configurations = getConfigurationAdmin().listConfigurations(filter);
if (configurations != null && configurations.length > 0)
{
diff --git a/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/FileInstall.java b/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/FileInstall.java
index 0dd3b28..aa9818d 100644
--- a/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/FileInstall.java
+++ b/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/FileInstall.java
@@ -22,6 +22,8 @@
import org.apache.felix.fileinstall.*;
import org.apache.felix.fileinstall.internal.Util.Logger;
+import org.apache.felix.utils.collections.DictionaryAsMap;
+import org.apache.felix.utils.properties.InterpolationHelper;
import org.osgi.framework.*;
import org.osgi.service.cm.*;
import org.osgi.service.packageadmin.PackageAdmin;
@@ -185,7 +187,7 @@
public void updated(String pid, Dictionary properties)
{
- Util.performSubstitution(properties);
+ InterpolationHelper.performSubstitution(new DictionaryAsMap(properties));
DirectoryWatcher watcher = null;
synchronized (watchers)
{
@@ -334,14 +336,19 @@
{
ConfigurationAdmin cm = (ConfigurationAdmin) super.addingService(serviceReference);
configInstaller = new ConfigInstaller(context, cm);
+ configInstaller.init();
fileInstall.addListener(configInstaller);
return cm;
}
public void removedService(ServiceReference serviceReference, Object o)
{
- configInstaller = null;
- fileInstall.removeListener(configInstaller);
+ if (configInstaller != null)
+ {
+ configInstaller.destroy();
+ fileInstall.removeListener(configInstaller);
+ configInstaller = null;
+ }
super.removedService(serviceReference, o);
}
}
diff --git a/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/Util.java b/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/Util.java
index e712a26..e6a7713 100644
--- a/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/Util.java
+++ b/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/Util.java
@@ -44,154 +44,8 @@
public class Util
{
- private static final char ESCAPE_CHAR = '\\';
- private static final String DELIM_START = "${";
- private static final String DELIM_STOP = "}";
-
private static final String CHECKSUM_SUFFIX = ".checksum";
- /**
- * 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
- * <tt>${<prop-name>}</tt>, where <tt><prop-name></tt>
- * refers to either a configuration property or a system property,
- * then the corresponding property value is substituted for the variable
- * placeholder. Multiple variable placeholders may exist in the
- * specified value as well as nested variable placeholders, which
- * are substituted from inner most to outer most. Configuration
- * properties override system properties.
- * </p>
- * @param val The string on which to perform property substitution.
- * @param currentKey The key of the property being evaluated used to
- * detect cycles.
- * @param cycleMap Map of variable references used to detect nested cycles.
- * @param configProps Set of configuration properties.
- * @return The value of the specified string after system property substitution.
- * @throws IllegalArgumentException If there was a syntax error in the
- * property placeholder syntax or a recursive variable reference.
- **/
- public static String substVars(String val, String currentKey, Map cycleMap, Dictionary configProps)
- throws IllegalArgumentException
- {
- if (cycleMap == null)
- {
- cycleMap = new HashMap();
- }
-
- // Put the current key in the cycle map.
- cycleMap.put(currentKey, currentKey);
-
- // Assume we have a value that is something like:
- // "leading ${foo.${bar}} middle ${baz} trailing"
-
- // Find the first ending '}' variable delimiter, which
- // will correspond to the first deepest nested variable
- // placeholder.
- int stopDelim = val.indexOf(DELIM_STOP);
- while (stopDelim > 0 && val.charAt(stopDelim - 1) == ESCAPE_CHAR)
- {
- stopDelim = val.indexOf(DELIM_STOP, stopDelim + 1);
- }
-
- // Find the matching starting "${" variable delimiter
- // by looping until we find a start delimiter that is
- // greater than the stop delimiter we have found.
- int startDelim = val.indexOf(DELIM_START);
- while (stopDelim >= 0)
- {
- int idx = val.indexOf(DELIM_START, startDelim + DELIM_START.length());
- if ((idx < 0) || (idx > stopDelim))
- {
- break;
- }
- else if (idx < stopDelim)
- {
- startDelim = idx;
- }
- }
-
- // If we do not have a start or stop delimiter, then just
- // return the existing value.
- if ((startDelim < 0) || (stopDelim < 0))
- {
- return unescape(val);
- }
-
- // At this point, we have found a variable placeholder so
- // we must perform a variable substitution on it.
- // Using the start and stop delimiter indices, extract
- // the first, deepest nested variable placeholder.
- String variable = val.substring(startDelim + DELIM_START.length(), stopDelim);
-
- // Verify that this is not a recursive variable reference.
- if (cycleMap.get(variable) != null)
- {
- throw new IllegalArgumentException("recursive variable reference: " + variable);
- }
-
- // Get the value of the deepest nested variable placeholder.
- // Try to configuration properties first.
- String substValue = (String) ((configProps != null) ? configProps.get(variable) : null);
- if (substValue == null)
- {
- // Ignore unknown property values.
- substValue = variable.length() > 0 ? System.getProperty(variable, "") : "";
- }
-
- // Remove the found variable from the cycle map, since
- // it may appear more than once in the value and we don't
- // want such situations to appear as a recursive reference.
- cycleMap.remove(variable);
-
- // Append the leading characters, the substituted value of
- // the variable, and the trailing characters to get the new
- // value.
- val = val.substring(0, startDelim) + substValue + val.substring(stopDelim + DELIM_STOP.length(), val.length());
-
- // Now perform substitution again, since there could still
- // be substitutions to make.
- val = substVars(val, currentKey, cycleMap, configProps);
-
- // Remove escape characters preceding {, } and \
- val = unescape(val);
-
- // Return the value.
- return val;
- }
-
- private static String unescape(String val) {
- int escape = val.indexOf(ESCAPE_CHAR);
- while (escape >= 0 && escape < val.length() - 1)
- {
- char c = val.charAt(escape + 1);
- if (c == '{' || c == '}' || c == ESCAPE_CHAR)
- {
- val = val.substring(0, escape) + val.substring(escape + 1);
- }
- escape = val.indexOf(ESCAPE_CHAR, escape + 1);
- }
- return val;
- }
-
public static int getGlobalLogLevel(BundleContext context)
{
String s = (String) context.getProperty(DirectoryWatcher.LOG_LEVEL);
diff --git a/fileinstall/src/test/java/org/apache/felix/fileinstall/internal/ConfigInstallerTest.java b/fileinstall/src/test/java/org/apache/felix/fileinstall/internal/ConfigInstallerTest.java
index 9794744..6d0095f 100644
--- a/fileinstall/src/test/java/org/apache/felix/fileinstall/internal/ConfigInstallerTest.java
+++ b/fileinstall/src/test/java/org/apache/felix/fileinstall/internal/ConfigInstallerTest.java
@@ -94,7 +94,7 @@
ConfigInstaller ci = new ConfigInstaller( mockBundleContext, mockConfigurationAdmin );
- assertEquals( "Factory configuration retrieved", mockConfiguration, ci.getConfiguration( "pid", "factoryPid" ) );
+ assertEquals( "Factory configuration retrieved", mockConfiguration, ci.getConfiguration( "pid-factoryPid.cfg", "pid", "factoryPid" ) );
mockConfigurationAdminControl.verify();
mockConfigurationControl.verify();
@@ -115,7 +115,7 @@
ConfigInstaller ci = new ConfigInstaller( mockBundleContext, mockConfigurationAdmin );
- assertEquals( "Factory configuration retrieved", mockConfiguration, ci.getConfiguration( "pid", "factoryPid" ) );
+ assertEquals( "Factory configuration retrieved", mockConfiguration, ci.getConfiguration( "pid-factoryPid.cfg","pid", "factoryPid" ) );
mockConfigurationAdminControl.verify();
mockConfigurationControl.verify();
@@ -136,7 +136,7 @@
ConfigInstaller ci = new ConfigInstaller( mockBundleContext, mockConfigurationAdmin );
- assertEquals( "Factory configuration retrieved", mockConfiguration, ci.getConfiguration( "pid", null ) );
+ assertEquals( "Factory configuration retrieved", mockConfiguration, ci.getConfiguration( "pid.cfg", "pid", null ) );
mockConfigurationAdminControl.verify();
mockConfigurationControl.verify();
diff --git a/fileinstall/src/test/java/org/apache/felix/fileinstall/internal/UtilTest.java b/fileinstall/src/test/java/org/apache/felix/fileinstall/internal/UtilTest.java
deleted file mode 100644
index 343380d..0000000
--- a/fileinstall/src/test/java/org/apache/felix/fileinstall/internal/UtilTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.internal;
-
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.Hashtable;
-
-import junit.framework.TestCase;
-import org.apache.felix.fileinstall.internal.Util;
-
-public class UtilTest extends TestCase
-{
- public void testBasicSubstitution()
- {
- System.setProperty("value1", "sub_value1");
- Dictionary props = new Hashtable();
- props.put("key0", "value0");
- props.put("key1", "${value1}");
- props.put("key2", "${value2}");
-
- for (Enumeration e = props.keys(); e.hasMoreElements();)
- {
- String name = (String) e.nextElement();
- props.put(name,
- Util.substVars((String) props.get(name), name, null, props));
- }
-
- assertEquals("value0", props.get("key0"));
- assertEquals("sub_value1", props.get("key1"));
- assertEquals("", props.get("key2"));
-
- }
-
- public void testSubstitutionFailures()
- {
- assertEquals("a}", Util.substVars("a}", "b", null, new Hashtable()));
- assertEquals("${a", Util.substVars("${a", "b", null, new Hashtable()));
- }
-
- public void testEmptyVariable() {
- assertEquals("", Util.substVars("${}", "b", null, new Hashtable()));
- }
-
- public void testInnerSubst() {
- Dictionary props = new Hashtable();
- props.put("a", "b");
- props.put("b", "c");
- assertEquals("c", Util.substVars("${${a}}", "z", null, props));
- }
-
- public void testSubstLoop() {
- try {
- Util.substVars("${a}", "a", null, new Hashtable());
- fail("Should have thrown an exception");
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
- public void testSubstitutionEscape()
- {
- assertEquals("${a}", Util.substVars("$\\{a${#}\\}", "b", null, new Hashtable()));
- assertEquals("${a}", Util.substVars("$\\{a\\}${#}", "b", null, new Hashtable()));
- assertEquals("${a}", Util.substVars("$\\{a\\}", "b", null, new Hashtable()));
- }
-
-}
\ No newline at end of file