FELIX-11 Implement Configuration Admin (Initial Checkin)

git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@527592 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/configadmin/pom.xml b/configadmin/pom.xml
new file mode 100644
index 0000000..b24db04
--- /dev/null
+++ b/configadmin/pom.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>felix</artifactId>
+        <version>0.9.0-incubator-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>org.apache.felix.cm</artifactId>
+    <packaging>bundle</packaging>
+
+    <name>Apache Felix Configuration Admin Service</name>
+    <description>
+        Implementation of the OSGi Configuration Admin Service Specification 1.2
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>${pom.groupId}</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${pom.groupId}</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <version>0.9.0-incubator-SNAPSHOT</version>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-Category>osgi</Bundle-Category>
+                        <Export-Package>
+                            org.apache.felix.cm,
+                            org.apache.felix.cm.file,
+                            org.osgi.service.cm;version=1.2
+                        </Export-Package>
+                        <Private-Package>
+                            org.apache.felix.cm.*
+                        </Private-Package>
+                        <Bundle-Activator>
+                            org.apache.felix.cm.impl.ConfigurationManager
+                        </Bundle-Activator>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/configadmin/src/main/java/org/apache/felix/cm/PersistenceManager.java b/configadmin/src/main/java/org/apache/felix/cm/PersistenceManager.java
new file mode 100644
index 0000000..b64df44
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/PersistenceManager.java
@@ -0,0 +1,120 @@
+/* 
+ * 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.cm;
+
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Enumeration;
+
+
+/**
+ * The <code>PersistenceManager</code> interface defines the API to be
+ * implemented to support persisting configuration data. This interface may
+ * be implemented by bundles, which support storing configuration data in
+ * different locations.
+ * <p>
+ * The Apache Felix Configuration Admin Service bundles provides an
+ * implementation of this interface using the platform filesystem to store
+ * configuration data.
+ * <p>
+ * Implementations of this interface must support loading and storing
+ * <code>java.util.Dictionary</code> objects as defined in section 104.4.2,
+ * Configuration Properties, of the Configuration Admin Service Specification
+ * Version 1.2.
+ * <p>
+ * To make implementations of this interface available to the Configuration
+ * Admin Service they must be registered as service for this interface. The
+ * Configuration Admin Service will consider all registered services plus the
+ * default platform file system based implementation to load configuration data.
+ * To store new configuration data, the persistence manager service with the
+ * highest rank value - the <code>service.ranking</code> service property - is
+ * used. If no pesistence manager service has been registered, the platfrom
+ * file system based implementation is used.
+ *
+ * @author fmeschbe
+ */
+public interface PersistenceManager
+{
+
+    /**
+     * Returns <code>true</code> if a persisted <code>Dictionary</code> exists
+     * for the given <code>pid</code>. 
+     * 
+     * @param pid The identifier for the dictionary to test.
+     */
+    boolean exists( String pid );
+
+
+    /**
+     * Returns the <code>Dictionary</code> for the given <code>pid</code>.
+     * 
+     * @param pid The identifier for the dictionary to load.
+     * 
+     * @return The dictionary for the identifier. This must not be
+     *      <code>null</code> but may be empty.
+     *      
+     * @throws IOException If an error occurrs loading the dictionary. An
+     *      <code>IOException</code> must also be thrown if no dictionary
+     *      exists for the given identifier. 
+     */
+    Dictionary load( String pid ) throws IOException;
+
+
+    /**
+     * Returns an enumeration of all <code>Dictionary</code> objects known to 
+     * this persistence manager.
+     * <p>
+     * Implementations of this method are allowed to return lazy enumerations.
+     * That is, it is allowable for the enumeration to not return a dictionary
+     * if loading it results in an error.
+     * 
+     * @return A possibly empty Enumeration of all dictionaries.
+     * 
+     * @throws IOException If an error occurrs getting the dictionaries.
+     */
+    Enumeration getDictionaries() throws IOException;
+
+
+    /**
+     * Stores the <code>Dictionary</code> under the given <code>pid</code>.
+     * 
+     * @param pid The identifier of the dictionary.
+     * @param properties The <code>Dictionary</code> to store.
+     * 
+     * @throws IOException If an error occurrs storing the dictionary. If this
+     *      exception is thrown, it is expected, that
+     *      {@link #exists(String) exists(pid} returns <code>false</code>.
+     */
+    void store( String pid, Dictionary properties ) throws IOException;
+
+
+    /**
+     * Removes the <code>Dictionary</code> for the given <code>pid</code>. If
+     * such a dictionary does not exist, this method has no effect.
+     * 
+     * @param pid The identifier of the dictionary to delet.
+     * 
+     * @throws IOException If an error occurrs deleting the dictionary. This
+     *      exception must not be thrown if no dictionary with the given
+     *      identifier exists.
+     */
+    void delete( String pid ) throws IOException;
+
+}
\ No newline at end of file
diff --git a/configadmin/src/main/java/org/apache/felix/cm/file/ConfigurationHandler.java b/configadmin/src/main/java/org/apache/felix/cm/file/ConfigurationHandler.java
new file mode 100644
index 0000000..16992cd
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/file/ConfigurationHandler.java
@@ -0,0 +1,796 @@
+/* 
+ * 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.cm.file;
+
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PushbackReader;
+import java.io.Writer;
+import java.lang.reflect.Array;
+import java.util.BitSet;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Vector;
+
+
+/**
+ * The <code>ConfigurationHandler</code> class implements configuration reading
+ * form a <code>java.io.InputStream</code> and writing to a
+ * <code>java.io.OutputStream</code> on behalf of the
+ * {@link FilePersistenceManager} class.
+ * 
+ * <pre>
+ cfg = prop "=" value .
+ prop = symbolic-name . // 1.4.2 of OSGi Core Specification
+ symbolic-name = token { "." token } .
+ token = { [ 0..9 ] | [ a..z ] | [ A..Z ] | '_' | '-' } .
+ value = [ type ] ( "[" values "]" | "(" values ")" | simple ) .
+ values = simple { "," simple } .
+ simple = """ stringsimple """ .
+ type = // 1-char type code .
+ stringsimple = // quoted string representation of the value .
+ * </pre>
+ *
+ * @author fmeschbe
+ */
+public class ConfigurationHandler
+{
+    protected static final String ENCODING = "UTF-8";
+
+    protected static final int TOKEN_NAME = 'N';
+    protected static final int TOKEN_EQ = '=';
+    protected static final int TOKEN_ARR_OPEN = '[';
+    protected static final int TOKEN_ARR_CLOS = ']';
+    protected static final int TOKEN_VEC_OPEN = '(';
+    protected static final int TOKEN_VEC_CLOS = ')';
+    protected static final int TOKEN_COMMA = ',';
+    protected static final int TOKEN_VAL_OPEN = '"'; // '{';
+    protected static final int TOKEN_VAL_CLOS = '"'; // '}';
+
+    // simple types (string & primitive wrappers)
+    protected static final int TOKEN_SIMPLE_STRING = 'T';
+    protected static final int TOKEN_SIMPLE_INTEGER = 'I';
+    protected static final int TOKEN_SIMPLE_LONG = 'L';
+    protected static final int TOKEN_SIMPLE_FLOAT = 'F';
+    protected static final int TOKEN_SIMPLE_DOUBLE = 'D';
+    protected static final int TOKEN_SIMPLE_BYTE = 'X';
+    protected static final int TOKEN_SIMPLE_SHORT = 'S';
+    protected static final int TOKEN_SIMPLE_CHARACTER = 'C';
+    protected static final int TOKEN_SIMPLE_BOOLEAN = 'B';
+
+    // primitives
+    protected static final int TOKEN_PRIMITIVE_INT = 'i';
+    protected static final int TOKEN_PRIMITIVE_LONG = 'l';
+    protected static final int TOKEN_PRIMITIVE_FLOAT = 'f';
+    protected static final int TOKEN_PRIMITIVE_DOUBLE = 'd';
+    protected static final int TOKEN_PRIMITIVE_BYTE = 'x';
+    protected static final int TOKEN_PRIMITIVE_SHORT = 's';
+    protected static final int TOKEN_PRIMITIVE_CHAR = 'c';
+    protected static final int TOKEN_PRIMITIVE_BOOLEAN = 'b';
+
+    protected static final String CRLF = "\r\n";
+
+    protected static final Map type2Code;
+    protected static final Map code2Type;
+
+    // set of valid characters for "symblic-name"
+    private static final BitSet NAME_CHARS;
+    private static final BitSet TOKEN_CHARS;
+
+    static
+    {
+        type2Code = new HashMap();
+
+        // simple (exclusive String whose type code is not written)
+        type2Code.put( Integer.class, new Integer( TOKEN_SIMPLE_INTEGER ) );
+        type2Code.put( Long.class, new Integer( TOKEN_SIMPLE_LONG ) );
+        type2Code.put( Float.class, new Integer( TOKEN_SIMPLE_FLOAT ) );
+        type2Code.put( Double.class, new Integer( TOKEN_SIMPLE_DOUBLE ) );
+        type2Code.put( Byte.class, new Integer( TOKEN_SIMPLE_BYTE ) );
+        type2Code.put( Short.class, new Integer( TOKEN_SIMPLE_SHORT ) );
+        type2Code.put( Character.class, new Integer( TOKEN_SIMPLE_CHARACTER ) );
+        type2Code.put( Boolean.class, new Integer( TOKEN_SIMPLE_BOOLEAN ) );
+
+        // primitives
+        type2Code.put( Integer.TYPE, new Integer( TOKEN_PRIMITIVE_INT ) );
+        type2Code.put( Long.TYPE, new Integer( TOKEN_PRIMITIVE_LONG ) );
+        type2Code.put( Float.TYPE, new Integer( TOKEN_PRIMITIVE_FLOAT ) );
+        type2Code.put( Double.TYPE, new Integer( TOKEN_PRIMITIVE_DOUBLE ) );
+        type2Code.put( Byte.TYPE, new Integer( TOKEN_PRIMITIVE_BYTE ) );
+        type2Code.put( Short.TYPE, new Integer( TOKEN_PRIMITIVE_SHORT ) );
+        type2Code.put( Character.TYPE, new Integer( TOKEN_PRIMITIVE_CHAR ) );
+        type2Code.put( Boolean.TYPE, new Integer( TOKEN_PRIMITIVE_BOOLEAN ) );
+
+        // reverse map to map type codes to classes, string class mapping
+        // to be added manually, as the string type code is not written and
+        // hence not included in the type2Code map
+        code2Type = new HashMap();
+        for ( Iterator ti = type2Code.entrySet().iterator(); ti.hasNext(); )
+        {
+            Map.Entry entry = ( Map.Entry ) ti.next();
+            code2Type.put( entry.getValue(), entry.getKey() );
+        }
+        code2Type.put( new Integer( TOKEN_SIMPLE_STRING ), String.class );
+
+        NAME_CHARS = new BitSet();
+        for ( int i = '0'; i <= '9'; i++ )
+            NAME_CHARS.set( i );
+        for ( int i = 'a'; i <= 'z'; i++ )
+            NAME_CHARS.set( i );
+        for ( int i = 'A'; i <= 'Z'; i++ )
+            NAME_CHARS.set( i );
+        NAME_CHARS.set( '_' );
+        NAME_CHARS.set( '-' );
+        NAME_CHARS.set( '.' );
+
+        TOKEN_CHARS = new BitSet();
+        TOKEN_CHARS.set( TOKEN_EQ );
+        TOKEN_CHARS.set( TOKEN_ARR_OPEN );
+        TOKEN_CHARS.set( TOKEN_ARR_CLOS );
+        TOKEN_CHARS.set( TOKEN_VEC_OPEN );
+        TOKEN_CHARS.set( TOKEN_VEC_CLOS );
+        TOKEN_CHARS.set( TOKEN_COMMA );
+        TOKEN_CHARS.set( TOKEN_VAL_OPEN );
+        TOKEN_CHARS.set( TOKEN_VAL_CLOS );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_STRING );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_INTEGER );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_LONG );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_FLOAT );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_DOUBLE );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_BYTE );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_SHORT );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_CHARACTER );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_BOOLEAN );
+
+        // primitives
+        TOKEN_CHARS.set( TOKEN_PRIMITIVE_INT );
+        TOKEN_CHARS.set( TOKEN_PRIMITIVE_LONG );
+        TOKEN_CHARS.set( TOKEN_PRIMITIVE_FLOAT );
+        TOKEN_CHARS.set( TOKEN_PRIMITIVE_DOUBLE );
+        TOKEN_CHARS.set( TOKEN_PRIMITIVE_BYTE );
+        TOKEN_CHARS.set( TOKEN_PRIMITIVE_SHORT );
+        TOKEN_CHARS.set( TOKEN_PRIMITIVE_CHAR );
+        TOKEN_CHARS.set( TOKEN_PRIMITIVE_BOOLEAN );
+    }
+
+
+    /**
+     * Writes the configuration data from the <code>Dictionary</code> to the
+     * given <code>OutputStream</code>.
+     * <p>
+     * This method writes at the current location in the stream and does not
+     * close the outputstream.
+     * 
+     * @param out The <code>OutputStream</code> to write the configurtion data
+     *      to.
+     * @param properties The <code>Dictionary</code> to write.
+     * 
+     * @throws IOException If an error occurrs writing to the output stream.
+     */
+    public static void write( OutputStream out, Dictionary properties ) throws IOException
+    {
+        BufferedWriter bw = new BufferedWriter( new OutputStreamWriter( out, ENCODING ) );
+
+        for ( Enumeration ce = properties.keys(); ce.hasMoreElements(); )
+        {
+            String key = ( String ) ce.nextElement();
+
+            //     cfg = prop "=" value "." .
+            writeQuoted( bw, key );
+            bw.write( TOKEN_EQ );
+            writeValue( bw, properties.get( key ) );
+            bw.write( CRLF );
+        }
+
+        bw.flush();
+    }
+
+
+    /**
+     * Reads configuration data from the given <code>InputStream</code> and
+     * returns a new <code>Dictionary</code> object containing the data.
+     * <p>
+     * This method reads from the current location in the stream upto the end
+     * of the stream but does not close the stream at the end.
+     * 
+     * @param ins The <code>InputStream</code> from which to read the
+     *      configuration data.
+     *      
+     * @return A <code>Dictionary</code> object containing the configuration
+     *      data. This object may be empty if the stream contains no
+     *      configuration data.
+     *      
+     * @throws IOException If an error occurrs reading from the stream. This
+     *      exception is also thrown if a syntax error is encountered.
+     */
+    public static Dictionary read( InputStream ins ) throws IOException
+    {
+        return new ConfigurationHandler().readInternal( ins );
+    }
+
+
+    // private constructor, this class is not to be instantiated from the outside
+    private ConfigurationHandler()
+    {
+    }
+
+    //---------- Configuration Input Implementation ---------------------------
+
+    private int token;
+    private String tokenValue;
+    private int line;
+    private int pos;
+
+
+    private Dictionary readInternal( InputStream ins ) throws IOException
+    {
+        BufferedReader br = new BufferedReader( new InputStreamReader( ins, ENCODING ) );
+        PushbackReader pr = new PushbackReader( br, 1 );
+
+        token = 0;
+        tokenValue = null;
+        line = 0;
+        pos = 0;
+
+        Hashtable configuration = new Hashtable();
+        token = 0;
+        while ( nextToken( pr ) == TOKEN_NAME )
+        {
+            String key = tokenValue;
+
+            // expect equal sign
+            if ( nextToken( pr ) != TOKEN_EQ )
+            {
+                throw readFailure( token, TOKEN_EQ );
+            }
+
+            // expect the token value
+            Object value = readValue( pr );
+            if ( value != null )
+            {
+                configuration.put( key, value );
+            }
+        }
+
+        return configuration;
+    }
+
+
+    /**
+     value = type ( "[" values "]" | "(" values ")" | simple ) .
+     values = value { "," value } .
+     simple = "{" stringsimple "}" .
+     type = // 1-char type code .
+     stringsimple = // quoted string representation of the value .
+     * 
+     * @param pr
+     * @return
+     * @throws IOException
+     */
+    private Object readValue( PushbackReader pr ) throws IOException
+    {
+        // read (optional) type code
+        int type = read( pr );
+
+        // read value kind code if type code is not a value kinde code
+        int code;
+        if ( code2Type.containsKey( new Integer( type ) ) )
+        {
+            code = read( pr );
+        }
+        else
+        {
+            code = type;
+            type = TOKEN_SIMPLE_STRING;
+        }
+
+        switch ( code )
+        {
+            case TOKEN_ARR_OPEN:
+                return readArray( type, pr );
+
+            case TOKEN_VEC_OPEN:
+                return readVector( type, pr );
+
+            case TOKEN_VAL_OPEN:
+                return readSimple( type, pr );
+
+            default:
+                return null;
+        }
+    }
+
+
+    private Object readArray( int typeCode, PushbackReader pr ) throws IOException
+    {
+        Vector vector = new Vector();
+        for ( ;; )
+        {
+            if ( !checkNext( pr, TOKEN_VAL_OPEN ) )
+            {
+                return null;
+            }
+
+            Object value = readSimple( typeCode, pr );
+            if ( value == null )
+            {
+                // abort due to error
+                return null;
+            }
+
+            vector.add( value );
+
+            int c = read( pr );
+            if ( c == TOKEN_ARR_CLOS )
+            {
+                Class type = ( Class ) code2Type.get( new Integer( typeCode ) );
+                Object array = Array.newInstance( type, vector.size() );
+                for ( int i = 0; i < vector.size(); i++ )
+                {
+                    Array.set( array, i, vector.get( i ) );
+                }
+                return array;
+            }
+            else if ( c < 0 )
+            {
+                return null;
+            }
+            else if ( c != TOKEN_COMMA )
+            {
+                return null;
+            }
+        }
+    }
+
+
+    private Vector readVector( int typeCode, PushbackReader pr ) throws IOException
+    {
+        Vector vector = new Vector();
+        for ( ;; )
+        {
+            if ( !checkNext( pr, TOKEN_VAL_OPEN ) )
+            {
+                return null;
+            }
+
+            Object value = readSimple( typeCode, pr );
+            if ( value == null )
+            {
+                // abort due to error
+                return null;
+            }
+
+            vector.add( value );
+
+            int c = read( pr );
+            if ( c == TOKEN_VEC_CLOS )
+            {
+                return vector;
+            }
+            else if ( c < 0 )
+            {
+                return null;
+            }
+            else if ( c != TOKEN_COMMA )
+            {
+                return null;
+            }
+        }
+    }
+
+
+    private Object readSimple( int code, PushbackReader pr ) throws IOException
+    {
+        switch ( code )
+        {
+            case -1:
+                return null;
+
+            case TOKEN_SIMPLE_STRING:
+                return readQuoted( pr );
+
+                // Simple/Primitive, only use wrapper classes
+            case TOKEN_SIMPLE_INTEGER:
+            case TOKEN_PRIMITIVE_INT:
+                return Integer.valueOf( readQuoted( pr ) );
+
+            case TOKEN_SIMPLE_LONG:
+            case TOKEN_PRIMITIVE_LONG:
+                return Long.valueOf( readQuoted( pr ) );
+
+            case TOKEN_SIMPLE_FLOAT:
+            case TOKEN_PRIMITIVE_FLOAT:
+                int fBits = Integer.parseInt( readQuoted( pr ) );
+                return new Float( Float.intBitsToFloat( fBits ) );
+
+            case TOKEN_SIMPLE_DOUBLE:
+            case TOKEN_PRIMITIVE_DOUBLE:
+                long dBits = Long.parseLong( readQuoted( pr ) );
+                return new Double( Double.longBitsToDouble( dBits ) );
+
+            case TOKEN_SIMPLE_BYTE:
+            case TOKEN_PRIMITIVE_BYTE:
+                return Byte.valueOf( readQuoted( pr ) );
+
+            case TOKEN_SIMPLE_SHORT:
+            case TOKEN_PRIMITIVE_SHORT:
+                return Short.valueOf( readQuoted( pr ) );
+
+            case TOKEN_SIMPLE_CHARACTER:
+            case TOKEN_PRIMITIVE_CHAR:
+                String cString = readQuoted( pr );
+                if ( cString != null && cString.length() > 0 )
+                {
+                    return new Character( cString.charAt( 0 ) );
+                }
+                return null;
+
+            case TOKEN_SIMPLE_BOOLEAN:
+            case TOKEN_PRIMITIVE_BOOLEAN:
+                return Boolean.valueOf( readQuoted( pr ) );
+
+                // unknown type code
+            default:
+                return null;
+        }
+    }
+
+
+    private boolean checkNext( PushbackReader pr, int expected ) throws IOException
+    {
+        int next = read( pr );
+        if ( next < 0 )
+        {
+            return false;
+        }
+
+        if ( next == expected )
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+
+    private String readQuoted( PushbackReader pr ) throws IOException
+    {
+        StringBuffer buf = new StringBuffer();
+        for ( ;; )
+        {
+            int c = read( pr );
+            switch ( c )
+            {
+                // escaped character
+                case '\\':
+                    c = read( pr );
+                    switch ( c )
+                    {
+                        // well known escapes
+                        case 'b':
+                            buf.append( '\b' );
+                            break;
+                        case 't':
+                            buf.append( '\t' );
+                            break;
+                        case 'n':
+                            buf.append( '\n' );
+                            break;
+                        case 'f':
+                            buf.append( '\f' );
+                            break;
+                        case 'r':
+                            buf.append( '\r' );
+                            break;
+                        case 'u':// need 4 characters !
+                            char[] cbuf = new char[4];
+                            if ( read( pr, cbuf ) == 4 )
+                            {
+                                c = Integer.parseInt( new String( cbuf ), 16 );
+                                buf.append( ( char ) c );
+                            }
+                            break;
+
+                        // just an escaped character, unescape
+                        default:
+                            buf.append( ( char ) c );
+                    }
+                    break;
+
+                // eof
+                case -1: // fall through
+
+                    // separator token
+                case TOKEN_VAL_CLOS:
+                    return buf.toString();
+
+                    // no escaping
+                default:
+                    buf.append( ( char ) c );
+            }
+        }
+    }
+
+
+    private int nextToken( PushbackReader pr ) throws IOException
+    {
+        int c = ignorableWhiteSpace( pr );
+
+        // immediately return EOF
+        if ( c < 0 )
+        {
+            return ( token = c );
+        }
+
+        // check whether there is a name
+        if ( NAME_CHARS.get( c ) )
+        {
+            // read the property name
+            tokenValue = readName( pr, ( char ) c );
+            return ( token = TOKEN_NAME );
+        }
+
+        // check another token
+        if ( TOKEN_CHARS.get( c ) )
+        {
+            return ( token = c );
+        }
+
+        // unexpected character -> so what ??
+        return ( token = -1 );
+    }
+
+
+    private int ignorableWhiteSpace( PushbackReader pr ) throws IOException
+    {
+        int c = read( pr );
+        while ( c >= 0 && Character.isWhitespace( ( char ) c ) )
+        {
+            c = read( pr );
+        }
+        return c;
+    }
+
+
+    private String readName( PushbackReader pr, char firstChar ) throws IOException
+    {
+        StringBuffer buf = new StringBuffer();
+        buf.append( firstChar );
+
+        int c = read( pr );
+        while ( c >= 0 && NAME_CHARS.get( c ) )
+        {
+            buf.append( ( char ) c );
+            c = read( pr );
+        }
+        pr.unread( c );
+
+        if ( buf.charAt( 0 ) == '.' || buf.charAt( buf.length() - 1 ) == '.' )
+        {
+            throw new IOException( "Name (" + buf + ") must not start or end with a dot" );
+        }
+
+        return buf.toString();
+    }
+
+
+    private int read( PushbackReader pr ) throws IOException
+    {
+        int c = pr.read();
+        if ( c == '\r' )
+        {
+            int c1 = pr.read();
+            if ( c1 != '\n' )
+            {
+                pr.unread( c1 );
+            }
+            c = '\n';
+        }
+
+        if ( c == '\n' )
+        {
+            line++;
+            pos = 0;
+        }
+        else
+        {
+            pos++;
+        }
+
+        return c;
+    }
+
+
+    private int read( PushbackReader pr, char[] buf ) throws IOException
+    {
+        for ( int i = 0; i < buf.length; i++ )
+        {
+            int c = read( pr );
+            if ( c >= 0 )
+            {
+                buf[i] = ( char ) c;
+            }
+            else
+            {
+                return i;
+            }
+        }
+
+        return buf.length;
+    }
+
+
+    private IOException readFailure( int current, int expected )
+    {
+        return new IOException( "Unexpected token " + current + "; expected: " + expected + " (line=" + line + ", pos="
+            + pos + ")" );
+    }
+
+
+    //---------- Configuration Output Implementation --------------------------
+
+    private static void writeValue( Writer out, Object value ) throws IOException
+    {
+        Class clazz = value.getClass();
+        if ( clazz.isArray() )
+        {
+            writeArray( out, value );
+        }
+        else if ( clazz == Vector.class )
+        {
+            writeVector( out, ( Vector ) value );
+        }
+        else
+        {
+            writeType( out, clazz );
+            writeSimple( out, value );
+        }
+    }
+
+
+    private static void writeArray( Writer out, Object arrayValue ) throws IOException
+    {
+        int size = Array.getLength( arrayValue );
+        if ( size == 0 )
+        {
+            return;
+        }
+
+        writeType( out, arrayValue.getClass().getComponentType() );
+        out.write( TOKEN_ARR_OPEN );
+        for ( int i = 0; i < size; i++ )
+        {
+            if ( i > 0 )
+                out.write( TOKEN_COMMA );
+            writeSimple( out, Array.get( arrayValue, i ) );
+        }
+        out.write( TOKEN_ARR_CLOS );
+    }
+
+
+    private static void writeVector( Writer out, Vector vector ) throws IOException
+    {
+        if ( vector.isEmpty() )
+        {
+            return;
+        }
+
+        writeType( out, vector.get( 0 ).getClass() );
+        out.write( TOKEN_VEC_OPEN );
+        for ( int i = 0; i < vector.size(); i++ )
+        {
+            if ( i > 0 )
+                out.write( TOKEN_COMMA );
+            writeSimple( out, vector.get( i ) );
+        }
+        out.write( TOKEN_VEC_CLOS );
+    }
+
+
+    private static void writeType( Writer out, Class valueType ) throws IOException
+    {
+        Integer code = ( Integer ) type2Code.get( valueType );
+        if ( code != null )
+        {
+            out.write( ( char ) code.intValue() );
+        }
+    }
+
+
+    private static void writeSimple( Writer out, Object value ) throws IOException
+    {
+        if ( value instanceof Double )
+        {
+            double dVal = ( ( Double ) value ).doubleValue();
+            value = new Long( Double.doubleToRawLongBits( dVal ) );
+        }
+        else if ( value instanceof Float )
+        {
+            float fVal = ( ( Float ) value ).floatValue();
+            value = new Integer( Float.floatToRawIntBits( fVal ) );
+        }
+
+        out.write( TOKEN_VAL_OPEN );
+        writeQuoted( out, String.valueOf( value ) );
+        out.write( TOKEN_VAL_CLOS );
+    }
+
+
+    private static void writeQuoted( Writer out, String simple ) throws IOException
+    {
+        if ( simple == null || simple.length() == 0 )
+        {
+            return;
+        }
+
+        char c = 0;
+        int len = simple.length();
+        for ( int i = 0; i < len; i++ )
+        {
+            c = simple.charAt( i );
+            switch ( c )
+            {
+                case '\\':
+                case TOKEN_VAL_CLOS:
+                    out.write( '\\' );
+                    out.write( c );
+                    break;
+
+                // well known escapes
+                case '\b':
+                    out.write( "\\b" );
+                    break;
+                case '\t':
+                    out.write( "\\t" );
+                    break;
+                case '\n':
+                    out.write( "\\n" );
+                    break;
+                case '\f':
+                    out.write( "\\f" );
+                    break;
+                case '\r':
+                    out.write( "\\r" );
+                    break;
+
+                // other escaping
+                default:
+                    if ( c < ' ' )
+                    {
+                        String t = "000" + Integer.toHexString( c );
+                        out.write( "\\u" + t.substring( t.length() - 4 ) );
+                    }
+                    else
+                    {
+                        out.write( c );
+                    }
+            }
+        }
+    }
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/file/FilePersistenceManager.java b/configadmin/src/main/java/org/apache/felix/cm/file/FilePersistenceManager.java
new file mode 100644
index 0000000..8b99b0b
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/file/FilePersistenceManager.java
@@ -0,0 +1,436 @@
+/* 
+ * 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.cm.file;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.BitSet;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.NoSuchElementException;
+import java.util.Stack;
+
+import org.apache.felix.cm.PersistenceManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+
+
+/**
+ * The <code>FilePersistenceManager</code> class stores configuration data in
+ * properties-like files inside a given directory. All configuration files are
+ * located in the same directory.
+ * <p>
+ * The configuration directory may be set by using the
+ * {@link #FilePersistenceManager(String)} naming the path to the directry. When
+ * this persistence manager is used by the Configuration Admin Service, the
+ * location may be configured using the {@link #CM_CONFIG_DIR} bundle context
+ * property.
+ * <p>
+ * If the location is not set, the <code>config</code> directory in the current
+ * working directory (as set in the <code>user.dir</code> system property) is
+ * used. If the the location is set but, no such directory exists, the directory
+ * and any missing parent directories are created. If a file exists at the given
+ * location, the constructor fails.
+ * <p>
+ * Configuration files are created in the configuration directory by appending
+ * the extension <code>.config</code> to the PID of the configuration. The PID
+ * is converted into a relative path name by replacing enclosed dots to slashes.
+ * Non-<code>symbolic-name</code> characters in the PID are encoded with their
+ * Unicode character code in hexadecimal.
+ * <p>
+ * <table border="0" cellspacing="3" cellpadding="0">
+ * <tr><td colspan="2"><b>Examples of PID to name conversion:</td></tr>
+ * <tr><th>PID</th><th>Configuration File Name</th></tr>
+ * <tr><td><code>sample</code><td><code>sample.config</code></tr>
+ * <tr><td><code>org.apache.felix.log.LogService</code><td><code>org/apache/felix/log/LogService.config</code></tr>
+ * <tr><td><code>sample.flèche</code><td><code>sample/fl%00e8che.config</code></tr>
+ * </table>
+ *
+ * @author fmeschbe
+ */
+public class FilePersistenceManager implements PersistenceManager
+{
+
+    /**
+     * The extension of the configuration files.
+     */
+    private static final String FILE_EXT = ".config";
+
+    private static final BitSet VALID_PATH_CHARS;
+
+    /**
+     * The abstract path name of the configuration files.
+     */
+    private final File location;
+
+    // sets up this class defining the set of valid characters in path
+    // set getFile(String) for details.
+    static
+    {
+        VALID_PATH_CHARS = new BitSet();
+
+        for ( int i = 'a'; i <= 'z'; i++ )
+        {
+            VALID_PATH_CHARS.set( i );
+        }
+        for ( int i = 'A'; i <= 'Z'; i++ )
+        {
+            VALID_PATH_CHARS.set( i );
+        }
+        for ( int i = '0'; i <= '9'; i++ )
+        {
+            VALID_PATH_CHARS.set( i );
+        }
+        VALID_PATH_CHARS.set( File.separatorChar );
+        VALID_PATH_CHARS.set( ' ' );
+        VALID_PATH_CHARS.set( '-' );
+        VALID_PATH_CHARS.set( '_' );
+    }
+
+
+    /**
+     * Creates an instance of this persistence manager using the given location
+     * as the directory to store and retrieve the configuration files.
+     * 
+     * @param location The configuration file location. If this is
+     *      <code>null</code> the <code>config</code> directory below the current
+     *      working directory is used.
+     * 
+     * @throws IllegalArgumentException If the location exists but is not a
+     *      directory or does not exist and cannot be created.
+     */
+    public FilePersistenceManager( String location )
+    {
+        if ( location == null )
+        {
+            location = System.getProperty( "user.dir" ) + "/config";
+        }
+
+        // check the location
+        File locationFile = new File( location );
+        if ( !locationFile.isDirectory() )
+        {
+            if ( locationFile.exists() )
+            {
+                throw new IllegalArgumentException( location + " is not a directory" );
+            }
+
+            if ( !locationFile.mkdirs() )
+            {
+                throw new IllegalArgumentException( "Cannot create directory " + location );
+            }
+        }
+
+        this.location = locationFile;
+    }
+
+
+    /**
+     * Loads configuration data from the configuration location and returns
+     * it as <code>Dictionary</code> objects.
+     * <p>
+     * This method is a lazy implementation, which is just one configuration
+     * file ahead of the current enumeration location.
+     * 
+     * @return an enumeration of configuration data returned as instances of
+     *      the <code>Dictionary</code> class.
+     */
+    public Enumeration getDictionaries()
+    {
+        return new DictionaryEnumeration();
+    }
+
+
+    /**
+     * Deletes the file for the given identifier.
+     * 
+     * @param pid The identifier of the configuration file to delete.
+     */
+    public void delete( String pid )
+    {
+        getFile( pid ).delete();
+    }
+
+
+    /**
+     * Returns <code>true</code> if a (configuration) file exists for the given
+     * identifier.
+     * 
+     * @param pid The identifier of the configuration file to check.
+     * 
+     * @return <code>true</code> if the file exists
+     */
+    public boolean exists( String pid )
+    {
+        return getFile( pid ).isFile();
+    }
+
+
+    /**
+     * Reads the (configuration) for the given identifier into a
+     * <code>Dictionary</code> object.
+     * 
+     * @param pid The identifier of the configuration file to delete.
+     * 
+     * @return The configuration read from the file. This <code>Dictionary</code>
+     *      may be empty if the file contains no configuration information
+     *      or is not properly formatted.
+     */
+    public Dictionary load( String pid ) throws IOException
+    {
+        return load( getFile( pid ) );
+    }
+
+
+    /**
+     * Stores the contents of the <code>Dictionary</code> in a file denoted
+     * by the given identifier.
+     * 
+     * @param pid The identifier of the configuration file to which to write
+     *      the configuration contents.
+     * @param props The configuration data to write.
+     * 
+     * @throws IOException If an error occurrs writing the configuration data.
+     */
+    public void store( String pid, Dictionary props ) throws IOException
+    {
+        OutputStream out = null;
+        try
+        {
+            File cfgFile = getFile( pid );
+            
+            // ensure parent path
+            cfgFile.getParentFile().mkdirs();
+
+            
+            out = new FileOutputStream( cfgFile );
+            ConfigurationHandler.write( out, props );
+        }
+        finally
+        {
+            if ( out != null )
+            {
+                try
+                {
+                    out.close();
+                }
+                catch ( IOException ioe )
+                {
+                    // ignore
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Loads the contents of the <code>cfgFile</code> into a new
+     * <code>Dictionary</code> object.
+     * 
+     * @param cfgFile The file from which to load the data.
+     * 
+     * @return A new <code>Dictionary</code> object providing the file contents.
+     * 
+     * @throws java.io.FileNotFoundException If the given file does not exist.
+     * @throws IOException If an error occurrs reading the configuration file.
+     */
+    private Dictionary load( File cfgFile ) throws IOException
+    {
+        InputStream ins = null;
+        try
+        {
+            ins = new FileInputStream( cfgFile );
+            return ConfigurationHandler.read( ins );
+        }
+        finally
+        {
+            if ( ins != null )
+            {
+                try
+                {
+                    ins.close();
+                }
+                catch ( IOException ioe )
+                {
+                    // ignore
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Creates an abstract path name for the <code>pid</code> encoding it as
+     * follows:
+     * <ul>
+     * <li>Dots (<code>.</code>) are replaced by <code>File.separatorChar</code>
+     * <li>Characters not matching [a-zA-Z0-9 _-] are encoded with a percent
+     *  character (<code>%</code>) and a 4-place hexadecimal unicode value.
+     * </ul>
+     * Before returning the path name, the parent directory and any ancestors
+     * are created.
+     *  
+     * @param pid The identifier for which to create the abstract file name.
+     * 
+     * @return The abstract path name, which the parent directory path created.
+     */
+    private File getFile( String pid )
+    {
+        // replace dots by File.separatorChar
+        pid = pid.replace( '.', File.separatorChar );
+
+        // replace slash by File.separatorChar if different
+        if ( File.separatorChar != '/' )
+        {
+            pid = pid.replace( '/', File.separatorChar );
+        }
+
+        // scan for first non-valid character (if any)
+        int first = 0;
+        while ( first < pid.length() && VALID_PATH_CHARS.get( pid.charAt( first ) ) )
+        {
+            first++;
+        }
+
+        // check whether we exhausted
+        if ( first < pid.length() )
+        {
+            StringBuffer buf = new StringBuffer( pid.substring( 0, first ) );
+
+            for ( int i = first; i < pid.length(); i++ )
+            {
+                char c = pid.charAt( i );
+                if ( VALID_PATH_CHARS.get( c ) )
+                {
+                    buf.append( c );
+                }
+                else
+                {
+                    String val = "000" + Integer.toHexString( c );
+                    buf.append( '%' );
+                    buf.append( val.substring( val.length() - 4 ) );
+                }
+            }
+
+            pid = buf.toString();
+        }
+
+        return new File( location, pid + FILE_EXT );
+    }
+
+    /**
+     * The <code>DictionaryEnumeration</code> class implements the
+     * <code>Enumeration</code> returning configuration <code>Dictionary</code>
+     * objects on behalf of the {@link FilePersistenceManager#getDictionaries()}
+     * method.
+     * <p>
+     * This enumeration loads configuration lazily with a look ahead of one
+     * dictionary.
+     *
+     * @author fmeschbe
+     */
+    private class DictionaryEnumeration implements Enumeration
+    {
+        private Stack dirStack;
+        private File[] fileList;
+        private int idx;
+        private Dictionary next;
+
+
+        DictionaryEnumeration()
+        {
+            dirStack = new Stack();
+            fileList = null;
+            idx = 0;
+
+            dirStack.push( location );
+            next = seek();
+        }
+
+
+        public boolean hasMoreElements()
+        {
+            return next != null;
+        }
+
+
+        public Object nextElement()
+        {
+            if ( next == null )
+            {
+                throw new NoSuchElementException();
+            }
+
+            Dictionary toReturn = next;
+            next = seek();
+            return toReturn;
+        }
+
+
+        private Dictionary seek()
+        {
+            while ( ( fileList != null && idx < fileList.length ) || !dirStack.isEmpty() )
+            {
+                if ( fileList == null || idx >= fileList.length )
+                {
+                    File dir = ( File ) dirStack.pop();
+                    fileList = dir.listFiles();
+                    idx = 0;
+                }
+                else
+                {
+
+                    File cfgFile = fileList[idx++];
+                    if ( cfgFile.isFile() )
+                    {
+                        try
+                        {
+                            Dictionary dict =  load( cfgFile );
+                            
+                            // use the dictionary if it has a PID and the PID
+                            // derived file name matches the source file name 
+                            if ( dict.get( Constants.SERVICE_PID ) != null
+                                && cfgFile.equals( getFile( ( String ) dict.get( Constants.SERVICE_PID ) ) ) )
+                            {
+                                return dict;
+                            }
+                        }
+                        catch ( IOException ioe )
+                        {
+                            // ignore, check next file
+                        }
+                    }
+                    else if ( cfgFile.isDirectory() )
+                    {
+                        dirStack.push( cfgFile );
+                    }
+                }
+            }
+
+            // exhausted
+            return null;
+        }
+    }
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/CaseInsensitiveDictionary.java b/configadmin/src/main/java/org/apache/felix/cm/impl/CaseInsensitiveDictionary.java
new file mode 100644
index 0000000..39b992d
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/CaseInsensitiveDictionary.java
@@ -0,0 +1,266 @@
+/* 
+ * 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.cm.impl;
+
+
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+
+/**
+ * The <code>CaseInsensitiveDictionary</code> is a
+ * <code>java.util.Dictionary</code> which conforms to the requirements laid
+ * out by the Configuration Admin Service Specification requiring the property
+ * names to keep case but to ignore case when accessing the properties.
+ *
+ * @author fmeschbe
+ */
+class CaseInsensitiveDictionary extends Dictionary
+{
+
+    /**
+     * The backend dictionary with lower case keys.
+     */
+    private Hashtable internalMap;
+
+    /**
+     * Mapping of lower case keys to original case keys as last used to set
+     * a property value.
+     */
+    private Hashtable originalKeys;
+
+
+    CaseInsensitiveDictionary()
+    {
+        internalMap = new Hashtable();
+        originalKeys = new Hashtable();
+    }
+
+
+    CaseInsensitiveDictionary( Dictionary props )
+    {
+        this();
+
+        Enumeration keys = props.keys();
+        while ( keys.hasMoreElements() )
+        {
+            Object key = keys.nextElement();
+            if ( !( key instanceof String ) )
+            {
+                throw new IllegalArgumentException( "Key [" + key + "] must be a String" );
+            }
+
+            // check uniqueness of key
+            String lowerCase = ( ( String ) key ).toLowerCase();
+            if ( internalMap.containsKey( lowerCase ) )
+            {
+                throw new IllegalArgumentException( "Key [" + key + "] already present in different case" );
+            }
+
+            // check the value
+            Object value = props.get( key );
+            checkValue( value );
+
+            // add the key/value pair
+            internalMap.put( lowerCase, value );
+            originalKeys.put( lowerCase, key );
+        }
+    }
+
+
+    CaseInsensitiveDictionary( CaseInsensitiveDictionary props )
+    {
+        internalMap = new Hashtable( props.internalMap );
+        originalKeys = new Hashtable( props.originalKeys );
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.util.Dictionary#elements()
+     */
+    public Enumeration elements()
+    {
+        return Collections.enumeration( internalMap.values() );
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.util.Dictionary#get(java.lang.Object)
+     */
+    public Object get( Object key )
+    {
+        if ( key == null )
+        {
+            throw new NullPointerException( "key" );
+        }
+
+        String stringKey = String.valueOf( key ).toLowerCase();
+        return internalMap.get( stringKey );
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.util.Dictionary#isEmpty()
+     */
+    public boolean isEmpty()
+    {
+        return internalMap.isEmpty();
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.util.Dictionary#keys()
+     */
+    public Enumeration keys()
+    {
+        return Collections.enumeration( originalKeys.values() );
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.util.Dictionary#put(java.lang.Object, java.lang.Object)
+     */
+    public Object put( Object key, Object value )
+    {
+        if ( key == null || value == null )
+        {
+            throw new NullPointerException( "key or value" );
+        }
+
+        if ( !( key instanceof String ) )
+        {
+            throw new IllegalArgumentException( "Key [" + key + "] must be a String" );
+        }
+
+        checkValue( value );
+
+        String lowerCase = String.valueOf( key ).toLowerCase();
+        originalKeys.put( lowerCase, key );
+        return internalMap.put( lowerCase, value );
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.util.Dictionary#remove(java.lang.Object)
+     */
+    public Object remove( Object key )
+    {
+        if ( key == null )
+        {
+            throw new NullPointerException( "key" );
+        }
+
+        String lowerCase = String.valueOf( key ).toLowerCase();
+        originalKeys.remove( lowerCase );
+        return internalMap.remove( lowerCase );
+    }
+
+
+    /* (non-Javadoc)
+     * @see java.util.Dictionary#size()
+     */
+    public int size()
+    {
+        return internalMap.size();
+    }
+
+
+    //---------- internal -----------------------------------------------------
+
+    static void checkValue( Object value )
+    {
+        Class type;
+        if ( value instanceof Object[] )
+        {
+            // check simple or primitive
+            type = value.getClass().getComponentType();
+
+            // check for primitive type
+            if ( type == Integer.TYPE || type == Long.TYPE || type == Float.TYPE || type == Double.TYPE
+                || type == Byte.TYPE || type == Short.TYPE || type == Character.TYPE || type == Boolean.TYPE )
+            {
+                return;
+            }
+
+        }
+        else if ( value instanceof Vector )
+        {
+            // check simple
+            Vector vector = ( Vector ) value;
+            if ( vector.isEmpty() )
+            {
+                throw new IllegalArgumentException( "Vector must not be empty" );
+            }
+
+            // ensure all elements have the same type
+            type = null;
+            for ( int i = 0; i < vector.size(); i++ )
+            {
+                Object el = vector.get( i );
+                if ( el == null )
+                {
+                    throw new IllegalArgumentException( "Vector must not contain null elements" );
+                }
+                if ( type == null )
+                {
+                    type = el.getClass();
+                }
+                else if ( type != el.getClass() )
+                {
+                    throw new IllegalArgumentException( "Vector element types must not be mixed" );
+                }
+            }
+
+        }
+        else if ( value != null )
+        {
+            // get the type to check (must be simple)
+            type = value.getClass();
+
+        }
+        else
+        {
+            // null is illegal
+            throw new IllegalArgumentException( "Value must not be null" );
+        }
+
+        // check for simple type
+        if ( type == String.class || type == Integer.class || type == Long.class || type == Float.class
+            || type == Double.class || type == Byte.class || type == Short.class || type == Character.class
+            || type == Boolean.class )
+        {
+            return;
+        }
+
+        // not a valid type
+        throw new IllegalArgumentException( "Value [" + value + "] has unsupported (base-) type " + type );
+    }
+
+
+    //---------- Object Overwrites --------------------------------------------
+
+    public String toString()
+    {
+        return internalMap.toString();
+    }
+
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdapter.java b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdapter.java
new file mode 100644
index 0000000..7f9b22d
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdapter.java
@@ -0,0 +1,176 @@
+/*
+ * $Url: $
+ * $Id$
+ *
+ * Copyright 1997-2005 Day Management AG
+ * Barfuesserplatz 6, 4001 Basel, Switzerland
+ * All Rights Reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Day Management AG, ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Day.
+ */
+package org.apache.felix.cm.impl;
+
+
+import java.io.IOException;
+import java.util.Dictionary;
+
+import org.osgi.service.cm.Configuration;
+
+
+/**
+ * The <code>ConfigurationAdapter</code> TODO
+ *
+ * @author fmeschbe
+ * @version $Rev:$, $Date:$
+ */
+public class ConfigurationAdapter implements Configuration
+{
+
+    private ConfigurationAdminImpl configurationAdmin;
+    private ConfigurationImpl delegatee;
+
+
+    ConfigurationAdapter( ConfigurationAdminImpl configurationAdmin, ConfigurationImpl delegatee )
+    {
+        this.configurationAdmin = configurationAdmin;
+        this.delegatee = delegatee;
+    }
+
+
+    /**
+     * @return
+     * @see org.apache.felix.cm.impl.ConfigurationImpl#getPid()
+     */
+    public String getPid()
+    {
+        checkDeleted();
+        return delegatee.getPid();
+    }
+
+
+    /**
+     * @return
+     * @see org.apache.felix.cm.impl.ConfigurationImpl#getFactoryPid()
+     */
+    public String getFactoryPid()
+    {
+        checkDeleted();
+        return delegatee.getFactoryPid();
+    }
+
+
+    /**
+     * @return
+     * @see org.apache.felix.cm.impl.ConfigurationImpl#getBundleLocation()
+     */
+    public String getBundleLocation()
+    {
+        configurationAdmin.checkPermission();
+        checkDeleted();
+        return delegatee.getBundleLocation();
+    }
+
+
+    /**
+     * @param bundleLocation
+     * @see org.apache.felix.cm.impl.ConfigurationImpl#setBundleLocation(java.lang.String)
+     */
+    public void setBundleLocation( String bundleLocation )
+    {
+        configurationAdmin.checkPermission();
+        checkDeleted();
+        delegatee.setBundleLocation( bundleLocation );
+    }
+
+
+    /**
+     * @throws IOException
+     * @see org.apache.felix.cm.impl.ConfigurationImpl#update()
+     */
+    public void update() throws IOException
+    {
+        checkDeleted();
+        delegatee.update();
+    }
+
+
+    /**
+     * @param properties
+     * @throws IOException
+     * @see org.apache.felix.cm.impl.ConfigurationImpl#update(java.util.Dictionary)
+     */
+    public void update( Dictionary properties ) throws IOException
+    {
+        checkDeleted();
+        delegatee.update( properties );
+    }
+
+
+    /**
+     * @return
+     * @see org.apache.felix.cm.impl.ConfigurationImpl#getProperties()
+     */
+    public Dictionary getProperties()
+    {
+        checkDeleted();
+        return delegatee.getProperties();
+    }
+
+
+    /**
+     * @throws IOException
+     * @see org.apache.felix.cm.impl.ConfigurationImpl#delete()
+     */
+    public void delete() throws IOException
+    {
+        checkDeleted();
+        delegatee.delete();
+    }
+
+
+    /**
+     * @return
+     * @see org.apache.felix.cm.impl.ConfigurationImpl#hashCode()
+     */
+    public int hashCode()
+    {
+        return delegatee.hashCode();
+    }
+
+
+    /**
+     * @param obj
+     * @return
+     * @see org.apache.felix.cm.impl.ConfigurationImpl#equals(java.lang.Object)
+     */
+    public boolean equals( Object obj )
+    {
+        return delegatee.equals( obj );
+    }
+
+
+    /**
+     * @return
+     * @see org.apache.felix.cm.impl.ConfigurationImpl#toString()
+     */
+    public String toString()
+    {
+        return delegatee.toString();
+    }
+
+    /**
+     * Checks whether this configuration object has already been deleted.
+     * 
+     * @throws IllegalStateException If this configuration object has been
+     *      deleted.
+     */
+    private void checkDeleted() {
+        if (delegatee.isDeleted()) {
+            throw new IllegalStateException( "Configuration " + delegatee.getPid() + " deleted" );
+        }
+    }
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdminFactory.java b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdminFactory.java
new file mode 100644
index 0000000..2719704
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdminFactory.java
@@ -0,0 +1,68 @@
+/* 
+ * 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.cm.impl;
+
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+
+
+/**
+ * The <code>ConfigurationAdminFactory</code> is the <code>ServiceFactory</code>
+ * registered as the <code>ConfigurationAdmin</code> service responsible to
+ * create the real <code>ConfiguratAdmin</code> instances returend to client
+ * bundles. Each bundle gets a separate instance.
+ *
+ * @author fmeschbe
+ */
+class ConfigurationAdminFactory implements ServiceFactory
+{
+
+    // The configuration manager to which the configuration admin instances
+    // delegate most of their work
+    private ConfigurationManager configurationManager;
+
+
+    ConfigurationAdminFactory( ConfigurationManager configurationManager )
+    {
+        this.configurationManager = configurationManager;
+    }
+
+
+    /**
+     * Returns a new instance of the {@link ConfigurationAdminImpl} class for
+     * the given bundle.
+     */
+    public Object getService( Bundle bundle, ServiceRegistration registration )
+    {
+        return new ConfigurationAdminImpl( configurationManager, bundle );
+    }
+
+
+    /**
+     * Disposes off the given {@link ConfigurationAdminImpl} instance as the
+     * given bundle has no use of it any more.
+     */
+    public void ungetService( Bundle bundle, ServiceRegistration registration, Object service )
+    {
+        ( ( ConfigurationAdminImpl ) service ).dispose();
+    }
+
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdminImpl.java b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdminImpl.java
new file mode 100644
index 0000000..9a91b2b
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationAdminImpl.java
@@ -0,0 +1,181 @@
+/* 
+ * 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.cm.impl;
+
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationPermission;
+
+
+/**
+ * The <code>ConfigurationAdminImpl</code> is the per-bundle frontend to the
+ * configuration manager. Instances of this class are created on-demand for
+ * each bundle trying to get hold of the <code>ConfigurationAdmin</code>
+ * service.
+ *
+ * @author fmeschbe
+ */
+public class ConfigurationAdminImpl implements ConfigurationAdmin
+{
+
+    // The configuration manager to which most of the tasks are delegated
+    private ConfigurationManager configurationManager;
+
+    // The bundle for which this instance has been created
+    private Bundle bundle;
+
+
+    ConfigurationAdminImpl( ConfigurationManager configurationManager, Bundle bundle )
+    {
+        this.configurationManager = configurationManager;
+        this.bundle = bundle;
+    }
+
+
+    void dispose()
+    {
+        this.bundle = null;
+        this.configurationManager = null;
+    }
+
+
+    Bundle getBundle()
+    {
+        return bundle;
+    }
+
+
+    //---------- ConfigurationAdmin interface ---------------------------------
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.cm.ConfigurationAdmin#createFactoryConfiguration(java.lang.String)
+     */
+    public Configuration createFactoryConfiguration( String factoryPid ) throws IOException
+    {
+        return wrap( configurationManager.createFactoryConfiguration( this, factoryPid ) );
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.cm.ConfigurationAdmin#createFactoryConfiguration(java.lang.String, java.lang.String)
+     */
+    public Configuration createFactoryConfiguration( String factoryPid, String location ) throws IOException
+    {
+        checkPermission();
+
+        return wrap( configurationManager.createFactoryConfiguration( factoryPid, location ) );
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.cm.ConfigurationAdmin#getConfiguration(java.lang.String)
+     */
+    public Configuration getConfiguration( String pid ) throws IOException
+    {
+        ConfigurationImpl config = configurationManager.getConfiguration( pid );
+
+        if ( config.getBundleLocation() == null )
+        {
+            config.setBundleLocation( getBundle().getLocation() );
+        }
+        else if ( !config.getBundleLocation().equals( getBundle().getLocation() ) )
+        {
+            checkPermission();
+        }
+
+        return wrap( config );
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.cm.ConfigurationAdmin#getConfiguration(java.lang.String, java.lang.String)
+     */
+    public Configuration getConfiguration( String pid, String location ) throws IOException
+    {
+        checkPermission();
+
+        return wrap( configurationManager.getConfiguration( pid, location ) );
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.cm.ConfigurationAdmin#listConfigurations(java.lang.String)
+     */
+    public Configuration[] listConfigurations( String filter ) throws IOException, InvalidSyntaxException
+    {
+        ConfigurationImpl ci[] = configurationManager.listConfigurations( this, filter );
+        if ( ci == null )
+        {
+            return null;
+        }
+
+        Configuration[] cfgs = new Configuration[ci.length];
+        for ( int i = 0; i < cfgs.length; i++ )
+        {
+            cfgs[i] = wrap( ci[i] );
+        }
+
+        return cfgs;
+    }
+
+
+    //---------- Security checks ----------------------------------------------
+
+    private Configuration wrap( ConfigurationImpl configuration )
+    {
+        return new ConfigurationAdapter( this, configuration );
+    }
+
+
+    /**
+     * Checks whether the bundle to which this instance has been given has the
+     * <code>CONFIGURE</code> permission and returns <code>true</code> in this
+     * case.
+     */
+    boolean hasPermission()
+    {
+        return bundle.hasPermission( new ConfigurationPermission( "*", ConfigurationPermission.CONFIGURE ) );
+    }
+
+
+    /**
+     * Checks whether the bundle to which this instance has been given has the
+     * <code>CONFIGURE</code> permission and throws a <code>SecurityException</code>
+     * if this is not the case.
+     * 
+     * @throws SecurityException if the bundle to which this instance belongs
+     *      does not have the <code>CONFIGURE</code> permission.
+     */
+    void checkPermission()
+    {
+        if ( !hasPermission() )
+        {
+            throw new SecurityException( "Bundle " + bundle.getSymbolicName()
+                + " not permitted for Configuration Tasks" );
+        }
+    }
+
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationImpl.java b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationImpl.java
new file mode 100644
index 0000000..b359652
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationImpl.java
@@ -0,0 +1,383 @@
+/* 
+ * 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.cm.impl;
+
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.felix.cm.PersistenceManager;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.log.LogService;
+
+
+/**
+ * The <code>ConfigurationImpl</code> is the implementation of the Configuration
+ * Admin Service Specification <i>Configuration object</i> (section 104.4).
+ *
+ * @author fmeschbe
+ */
+class ConfigurationImpl
+{
+
+    /**
+     * The {@link ConfigurationManager configuration manager} instance which
+     * caused this configuration object to be created.
+     */
+    private ConfigurationManager configurationManager;
+
+    /**
+     * The {@link PersistenceManager persistence manager} which loaded this
+     * configuration instance and which is used to store and delete configuration
+     * data.
+     */
+    private PersistenceManager persistenceManager;
+
+    /**
+     * The serviceReference PID of this configuration.
+     */
+    private String pid;
+
+    /**
+     * The factory serviceReference PID of this configuration or <code>null</code> if this
+     * is not a factory configuration.
+     */
+    private String factoryPID;
+
+    /**
+     * The location of the bundle to which this configuration instance is bound.
+     * This is not necessarily the same as the bundle of the
+     * {@link #configurationAdmin}. If this configuration is not bound to a
+     * bundle, this field is <code>null</code>.
+     */
+    private String bundleLocation;
+
+    /**
+     * The <code>ServiceReference</code> of the serviceReference which first asked for
+     * this configuration. This field is <code>null</code> if the configuration
+     * has not been handed to a serviceReference by way of the <code>ManagedService.update(Dictionary)</code> 
+     * or <code>ManagedServiceFactory.updated(String, Dictionary)</code>
+     * method.
+     */
+    private ServiceReference serviceReference;
+
+    /**
+     * The configuration data of this configuration instance. This is a private
+     * copy of the properties of which a copy is made when the
+     * {@link #getProperties()} method is called. This field is <code>null</code> if
+     * the configuration has been created and never stored to persistence.
+     */
+    private CaseInsensitiveDictionary properties;
+
+
+    ConfigurationImpl( ConfigurationManager configurationManager, PersistenceManager persistenceManager,
+        Dictionary properties )
+    {
+        this.configurationManager = configurationManager;
+        this.persistenceManager = persistenceManager;
+
+        this.pid = ( String ) properties.remove( Constants.SERVICE_PID );
+        this.factoryPID = ( String ) properties.remove( ConfigurationAdmin.SERVICE_FACTORYPID );
+        this.bundleLocation = ( String ) properties.remove( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
+
+        configure( properties );
+    }
+
+
+    ConfigurationImpl( ConfigurationManager configurationManager, PersistenceManager persistenceManager, String pid,
+        String factoryPid )
+    {
+        this.configurationManager = configurationManager;
+        this.persistenceManager = persistenceManager;
+        this.pid = pid;
+        this.factoryPID = factoryPid;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.cm.Configuration#delete()
+     */
+    public void delete() throws IOException
+    {
+        if ( !isDeleted() )
+        {
+            persistenceManager.delete( pid );
+            persistenceManager = null;
+
+            configurationManager.deleted( this );
+        }
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.cm.Configuration#getPid()
+     */
+    public String getPid()
+    {
+        return pid;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.cm.Configuration#getFactoryPid()
+     */
+    public String getFactoryPid()
+    {
+        return factoryPID;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.cm.Configuration#getBundleLocation()
+     */
+    public String getBundleLocation()
+    {
+        return bundleLocation;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.cm.Configuration#getProperties()
+     */
+    public Dictionary getProperties()
+    {
+        // no properties yet
+        if ( properties == null )
+        {
+            return null;
+        }
+
+        CaseInsensitiveDictionary props = new CaseInsensitiveDictionary( properties );
+
+        // fix special properties (pid, factory PID, bundle location)
+        setAutoProperties( props, false );
+
+        return props;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.cm.Configuration#setBundleLocation(java.lang.String)
+     */
+    public void setBundleLocation( String bundleLocation )
+    {
+        if ( !isDeleted() )
+        {
+            this.bundleLocation = bundleLocation;
+
+            // 104.15.2.8 The bundle location will be set persistently
+            try
+            {
+                store();
+            }
+            catch ( IOException ioe )
+            {
+                configurationManager.log( LogService.LOG_ERROR, "Persisting new bundle location failed", ioe );
+            }
+        }
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.cm.Configuration#update()
+     */
+    public void update() throws IOException
+    {
+        if ( !isDeleted() )
+        {
+            // read configuration from persistence (again)
+            Dictionary properties = persistenceManager.load( pid );
+
+            // ensure serviceReference pid
+            String servicePid = ( String ) properties.get( Constants.SERVICE_PID );
+            if ( servicePid != null && !pid.equals( servicePid ) )
+            {
+                throw new IOException( "PID of configuration file does match requested PID; expected " + pid + ", got "
+                    + servicePid );
+            }
+
+            configure( properties );
+
+            configurationManager.updated( this );
+        }
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.service.cm.Configuration#update(java.util.Dictionary)
+     */
+    public void update( Dictionary properties ) throws IOException
+    {
+        if ( !isDeleted() )
+        {
+            CaseInsensitiveDictionary newProperties = new CaseInsensitiveDictionary( properties );
+
+            setAutoProperties( newProperties, true );
+
+            persistenceManager.store( pid, newProperties );
+
+            configure( newProperties );
+
+            configurationManager.updated( this );
+        }
+    }
+
+
+    //---------- Object overwrites --------------------------------------------
+
+    public boolean equals( Object obj )
+    {
+        if ( obj == this )
+        {
+            return true;
+        }
+
+        if ( obj instanceof Configuration )
+        {
+            return pid.equals( ( ( Configuration ) obj ).getPid() );
+        }
+
+        return false;
+    }
+
+
+    public int hashCode()
+    {
+        return pid.hashCode();
+    }
+
+
+    public String toString()
+    {
+        return "Configuration PID=" + pid + ", factoryPID=" + factoryPID + ", bundleLocation=" + bundleLocation;
+    }
+
+
+    //---------- private helper -----------------------------------------------
+
+    void setServiceReference( ServiceReference serviceReference )
+    {
+        this.serviceReference = serviceReference;
+    }
+
+
+    ServiceReference getServiceReference()
+    {
+        return serviceReference;
+    }
+
+
+    void store() throws IOException
+    {
+        Dictionary props = getProperties();
+
+        // if this is a new configuration, we just use an empty Dictionary
+        if ( props == null )
+        {
+            props = new Hashtable();
+
+            // add automatic properties including the bundle location (if set)
+            setAutoProperties( props, true );
+        }
+        else if ( getBundleLocation() != null )
+        {
+            props.put( ConfigurationAdmin.SERVICE_BUNDLELOCATION, getBundleLocation() );
+        }
+
+        // only store now, if this is not a new configuration
+        persistenceManager.store( pid, props );
+    }
+
+
+    boolean isDeleted()
+    {
+        if ( persistenceManager != null )
+        {
+            if ( properties == null || persistenceManager.exists( pid ) )
+            {
+                return false;
+            }
+
+            persistenceManager = null;
+        }
+
+        return true;
+    }
+
+
+    private void configure( Dictionary properties )
+    {
+        // remove predefined properties
+        clearAutoProperties( properties );
+
+        // ensure CaseInsensitiveDictionary
+        if ( properties instanceof CaseInsensitiveDictionary )
+        {
+            this.properties = ( CaseInsensitiveDictionary ) properties;
+        }
+        else
+        {
+            this.properties = new CaseInsensitiveDictionary( properties );
+        }
+    }
+
+
+    void setAutoProperties( Dictionary properties, boolean withBundleLocation )
+    {
+        // set pid and factory pid in the properties
+        replaceProperty( properties, Constants.SERVICE_PID, pid );
+        replaceProperty( properties, ConfigurationAdmin.SERVICE_FACTORYPID, factoryPID );
+
+        // bundle location is not set here
+        if ( withBundleLocation )
+        {
+            replaceProperty( properties, ConfigurationAdmin.SERVICE_BUNDLELOCATION, getBundleLocation() );
+        }
+        else
+        {
+            properties.remove( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
+        }
+    }
+
+
+    void clearAutoProperties( Dictionary properties )
+    {
+        properties.remove( Constants.SERVICE_PID );
+        properties.remove( ConfigurationAdmin.SERVICE_FACTORYPID );
+        properties.remove( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
+    }
+
+
+    private void replaceProperty( Dictionary properties, String key, String value )
+    {
+        if ( value == null )
+        {
+            properties.remove( key );
+        }
+        else
+        {
+            properties.put( key, value );
+        }
+    }
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationManager.java b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationManager.java
new file mode 100644
index 0000000..d6b2190
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/ConfigurationManager.java
@@ -0,0 +1,1261 @@
+/* 
+ * 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.cm.impl;
+
+
+import java.awt.image.ImagingOpException;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.apache.felix.cm.PersistenceManager;
+import org.apache.felix.cm.file.FilePersistenceManager;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationEvent;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ConfigurationListener;
+import org.osgi.service.cm.ConfigurationPlugin;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.log.LogService;
+import org.osgi.util.tracker.ServiceTracker;
+
+
+/**
+ * The <code>ConfigurationManager</code> is the central class in this
+ * implementation of the Configuration Admin Service Specification. As such it
+ * has the following tasks:
+ * <ul>
+ * <li>It is a <code>BundleActivator</code> which is called when the bundle
+ * is started and stopped.
+ * <li>It is a <code>BundleListener</code> which gets informed when the
+ * states of bundles change. Mostly this is needed to unbind any bound
+ * configuration in case a bundle is uninstalled.
+ * <li>It is a <code>ServiceListener</code> which gets informed when
+ * <code>ManagedService</code> and <code>ManagedServiceFactory</code>
+ * services are registered and unregistered. This is used to provide
+ * configuration to these services. As a service listener it also listens for
+ * {@link PersistenceManager} instances being registered to support different
+ * configuration persistence layers.
+ * <li>A {@link ConfigurationAdminFactory} instance is registered as the
+ * <code>ConfigurationAdmin</code> service.
+ * <li>A {@link FilePersistenceManager} instance is registered as a default
+ * {@link PersistenceManager}.
+ * <li>Last but not least this instance manages all tasks laid out in the
+ * specification such as maintaining configuration, taking care of configuration
+ * events, etc.
+ * </ul>
+ * <p>
+ * The default {@link FilePersistenceManager} is configured with a configuration
+ * location taken from the <code>felix.cm.dir</code> framework property. If
+ * this property is not set the <code>config</code> directory in the current
+ * working directory as specified in the <code>user.dir</code> system property
+ * is used.
+ * 
+ * @author fmeschbe
+ */
+public class ConfigurationManager implements BundleActivator, BundleListener
+{
+
+    /**
+     * The name of the bundle context property defining the location for the
+     * configuration files (value is "felix.cm.dir").
+     * 
+     * @see #FilePersistenceManager(BundleContext)
+     */
+    public static final String CM_CONFIG_DIR = "felix.cm.dir";
+
+    // random number generator to create configuration PIDs for factory
+    // configurations
+    private static SecureRandom numberGenerator;
+
+    // comparator used to keep the ordered persistence manager map
+    private static final Comparator cmRankComp = new RankingComparator( true, ConfigurationPlugin.CM_RANKING );
+
+    // the BundleContext of the Configuration Admin Service bundle
+    private BundleContext bundleContext;
+
+    // the service reference to the ConfigurationAdmin service
+    private ServiceReference configurationAdminReference;
+
+    // the ServiceTracker to emit log services (see log(int, String, Throwable))
+    private ServiceTracker logTracker;
+
+    // the ConfigurationEvent listeners
+    private ServiceTracker configurationListenerTracker;
+
+    // service tracker for managed services
+    private ServiceTracker managedServiceTracker;
+
+    // service tracker for managed service factories
+    private ServiceTracker managedServiceFactoryTracker;
+
+    // PersistenceManager services
+    private ServiceTracker persistenceManagerTracker;
+
+    // the thread used to schedule tasks required to run asynchronously
+    private UpdateThread updateThread;
+
+    /**
+     * The actual list of {@link PersistenceManager persistence managers} to use
+     * when looking for configuration data. This list is built from the
+     * {@link #persistenceManagerMap}, which is ordered according to the
+     * {@link RankingComparator}.
+     */
+    private PersistenceManager[] persistenceManagers;
+
+    // the persistenceManagerTracker.getTrackingCount when the
+    // persistenceManagers were last got
+    private int pmtCount;
+
+    // the cache of Factory instances mapped by their factory PID
+    private Map factories;
+
+    // the cache of Configuration instances mapped by their PID
+    private Map configurations;
+
+
+    public void start( BundleContext bundleContext )
+    {
+        // track the log service using a ServiceTracker
+        logTracker = new ServiceTracker( bundleContext, LogService.class.getName(), null );
+        logTracker.open();
+
+        // set up some fields
+        this.bundleContext = bundleContext;
+        this.factories = new HashMap();
+        this.configurations = new HashMap();
+
+        // configurationlistener support
+        configurationListenerTracker = new ServiceTracker( bundleContext, ConfigurationListener.class.getName(), null );
+        configurationListenerTracker.open();
+
+        // initialize the asynchonous updater thread
+        this.updateThread = new UpdateThread( this );
+        this.updateThread.start();
+
+        // set up the location (might throw IllegalArgumentException)
+        try
+        {
+            FilePersistenceManager fpm = new FilePersistenceManager( bundleContext.getProperty( CM_CONFIG_DIR ) );
+            Hashtable props = new Hashtable();
+            props.put( Constants.SERVICE_PID, fpm.getClass().getName() );
+            props.put( Constants.SERVICE_DESCRIPTION, "Platform Filesystem Persistence Manager" );
+            props.put( Constants.SERVICE_VENDOR, "Apache Software Foundation" );
+            props.put( Constants.SERVICE_RANKING, new Integer( Integer.MIN_VALUE ) );
+            bundleContext.registerService( PersistenceManager.class.getName(), fpm, props );
+        }
+        catch ( IllegalArgumentException iae )
+        {
+            log( LogService.LOG_ERROR, "Cannot create the FilePersistenceManager", iae );
+        }
+
+        // register as bundle and service listener
+        bundleContext.addBundleListener( this );
+
+        // get all persistence managers to begin with
+        pmtCount = 1; // make sure to get the persistence managers at least
+        // once
+        persistenceManagerTracker = new ServiceTracker( bundleContext, PersistenceManager.class.getName(), null );
+        persistenceManagerTracker.open();
+
+        // create and register configuration admin - start after PM tracker ...
+        ConfigurationAdminFactory caf = new ConfigurationAdminFactory( this );
+        Hashtable props = new Hashtable();
+        props.put( Constants.SERVICE_PID, "org.apache.felix.cm.ConfigurationAdmin" );
+        props.put( Constants.SERVICE_DESCRIPTION, "Configuration Admin Service Specification 1.2 Implementation" );
+        props.put( Constants.SERVICE_VENDOR, "Apache Software Foundation" );
+        bundleContext.registerService( ConfigurationAdmin.class.getName(), caf, props );
+
+        // start handling ManagedService[Factory] services
+        managedServiceTracker = new ManagedServiceTracker();
+        managedServiceFactoryTracker = new ManagedServiceFactoryTracker();
+    }
+
+
+    public void stop( BundleContext bundleContext )
+    {
+        // stop handling ManagedService[Factory] services
+        managedServiceFactoryTracker.close();
+        managedServiceTracker.close();
+
+        // don't care for PersistenceManagers any more
+        persistenceManagerTracker.close();
+
+        // stop listening for events
+        bundleContext.removeBundleListener( this );
+
+        if ( configurationListenerTracker != null )
+        {
+            configurationListenerTracker.close();
+        }
+
+        if ( updateThread != null )
+        {
+            // terminate asynchrounous updates
+            updateThread.terminate();
+
+            // wait for all updates to terminate
+            try
+            {
+                updateThread.join();
+            }
+            catch ( InterruptedException ie )
+            {
+                // don't really care
+            }
+        }
+
+        if ( logTracker != null )
+        {
+            logTracker.close();
+        }
+
+        this.bundleContext = null;
+        this.configurations = null;
+    }
+
+
+    // ---------- Configuration caching support --------------------------------
+
+    ConfigurationImpl getCachedConfiguration( String pid )
+    {
+        synchronized ( configurations )
+        {
+            return ( ConfigurationImpl ) configurations.get( pid );
+        }
+    }
+
+
+    Iterator getCachedConfigurations()
+    {
+        synchronized ( configurations )
+        {
+            return configurations.values().iterator();
+        }
+    }
+
+
+    void cacheConfiguration( ConfigurationImpl configuration )
+    {
+        synchronized ( configurations )
+        {
+            configurations.put( configuration.getPid(), configuration );
+        }
+    }
+
+
+    void removeConfiguration( ConfigurationImpl configuration )
+    {
+        synchronized ( configurations )
+        {
+            configurations.remove( configuration.getPid() );
+        }
+    }
+
+
+    // ---------- ConfigurationAdminImpl support -------------------------------
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.osgi.service.cm.ConfigurationAdmin#createFactoryConfiguration(java.lang.String)
+     */
+    ConfigurationImpl createFactoryConfiguration( ConfigurationAdminImpl configurationAdmin, String factoryPid )
+        throws IOException
+    {
+        Factory factory = getFactory( factoryPid );
+
+        // check Persmission if factory is bound to another bundle
+        if ( factory.getBundleLocation() != null
+            && !factory.getBundleLocation().equals( configurationAdmin.getBundle().getLocation() ) )
+        {
+            configurationAdmin.checkPermission();
+        }
+
+        // create the configuration
+        String pid = createPid( factoryPid );
+        ConfigurationImpl config = createConfiguration( pid, factoryPid );
+        config.setBundleLocation( configurationAdmin.getBundle().getLocation() );
+
+        // add the configuration to the factory
+        factory.addPID( pid );
+        factory.store();
+
+        return config;
+    }
+
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.osgi.service.cm.ConfigurationAdmin#createFactoryConfiguration(java.lang.String,
+     *      java.lang.String)
+     */
+    ConfigurationImpl createFactoryConfiguration( String factoryPid, String location ) throws IOException
+    {
+        // create the configuration
+        String pid = createPid( factoryPid );
+        ConfigurationImpl config = createConfiguration( pid, factoryPid );
+        if ( location != null )
+        {
+            config.setBundleLocation( location );
+        }
+
+        // add the configuration to the factory
+        Factory factory = getFactory( factoryPid );
+        factory.addPID( pid );
+        factory.store();
+
+        return config;
+    }
+
+
+    ConfigurationImpl getConfiguration( String pid ) throws IOException
+    {
+        return getConfiguration( pid, true );
+    }
+
+
+    ConfigurationImpl getConfiguration( String pid, String bundleLocation ) throws IOException
+    {
+        ConfigurationImpl config = getConfiguration( pid, false );
+
+        if ( config == null )
+        {
+            config = createConfiguration( pid, null );
+            if ( bundleLocation != null )
+            {
+                config.setBundleLocation( bundleLocation );
+            }
+        }
+
+        return config;
+    }
+
+
+    ConfigurationImpl[] listConfigurations( ConfigurationAdminImpl configurationAdmin, String filterString )
+        throws IOException, InvalidSyntaxException
+    {
+        Filter filter = null;
+        if ( filterString != null )
+        {
+            filter = bundleContext.createFilter( filterString );
+        }
+
+        boolean unprivileged = configurationAdmin != null && !configurationAdmin.hasPermission();
+        String location = unprivileged ? configurationAdmin.getBundle().getLocation() : null;
+
+        List configList = new ArrayList();
+
+        PersistenceManager[] pmList = getPersistenceManagers();
+        for ( int i = 0; i < pmList.length; i++ )
+        {
+            Enumeration configs = pmList[i].getDictionaries();
+            while ( configs.hasMoreElements() )
+            {
+                Dictionary config = ( Dictionary ) configs.nextElement();
+
+                // ignore non-Configuration dictionaries
+                if ( config.get( Constants.SERVICE_PID ) == null )
+                {
+                    continue;
+                }
+
+                // ignore this config if not privileged and not bound to bundle
+                if ( unprivileged )
+                {
+                    Object boundLocation = config.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
+                    if ( !location.equals( boundLocation ) )
+                    {
+                        continue;
+                    }
+                }
+
+                // check filter
+                if ( filter == null || filter.match( config ) )
+                {
+                    configList.add( new ConfigurationImpl( this, pmList[i], config ) );
+                }
+            }
+        }
+
+        return ( org.apache.felix.cm.impl.ConfigurationImpl[] ) configList.toArray( new ConfigurationImpl[configList
+            .size()] );
+    }
+
+
+    void deleted( ConfigurationImpl config )
+    {
+        // remove the configuration from the cache
+        removeConfiguration( config );
+        updateThread.schedule( new DeleteConfiguration( config ) );
+    }
+
+
+    void updated( ConfigurationImpl config )
+    {
+        updateThread.schedule( new UpdateConfiguration( config ) );
+    }
+
+
+    void fireConfigurationEvent( int type, ConfigurationImpl config )
+    {
+
+        updateThread.schedule( new FireConfigurationEvent( type, config ) );
+    }
+
+
+    // ---------- BundleListener -----------------------------------------------
+
+    public void bundleChanged( BundleEvent event )
+    {
+        if ( event.getType() == BundleEvent.UNINSTALLED )
+        {
+            String location = event.getBundle().getLocation();
+            String filter = "(service.bundleLocation=" + location + ")";
+
+            try
+            {
+                ConfigurationImpl[] configs = listConfigurations( null, filter );
+                if ( configs != null && configs.length > 0 )
+                {
+                    for ( int i = 0; i < configs.length; i++ )
+                    {
+                        configs[i].setBundleLocation( null );
+                        try
+                        {
+                            configs[i].store();
+                        }
+                        catch ( IOException ioe )
+                        {
+                            log( LogService.LOG_WARNING,
+                                "Problem storing unbound configuration " + configs[i].getPid(), ioe );
+                        }
+                    }
+                }
+            }
+            catch ( Exception e )
+            {
+                log( LogService.LOG_WARNING, "Problem unbinding configurations for bundle " + location, e );
+            }
+
+            // unbind cached configurations
+            Iterator configurations = getCachedConfigurations();
+            while ( configurations.hasNext() )
+            {
+                ConfigurationImpl cfg = ( ConfigurationImpl ) configurations.next();
+                if ( location.equals( cfg.getBundleLocation() ) )
+                {
+                    cfg.setBundleLocation( null );
+                }
+            }
+
+            // TODO: find factories to unbind !!
+        }
+    }
+
+
+    // ---------- internal -----------------------------------------------------
+
+    private PersistenceManager[] getPersistenceManagers()
+    {
+        if ( persistenceManagers == null || persistenceManagerTracker.getTrackingCount() > pmtCount )
+        {
+
+            ServiceReference[] refs = persistenceManagerTracker.getServiceReferences();
+            if ( refs == null || refs.length == 0 )
+            {
+                return new PersistenceManager[0];
+            }
+
+            SortedSet pms = new TreeSet( cmRankComp );
+            for ( int i = 0; i < refs.length; i++ )
+            {
+                pms.add( persistenceManagerTracker.getService( refs[i] ) );
+            }
+
+            persistenceManagers = ( PersistenceManager[] ) pms.toArray( new PersistenceManager[pms.size()] );
+        }
+
+        return persistenceManagers;
+    }
+
+
+    private void configure( ServiceReference sr, ManagedService service )
+    {
+        String pid = ( String ) sr.getProperty( Constants.SERVICE_PID );
+        if ( pid != null )
+        {
+            ManagedServiceUpdate update = new ManagedServiceUpdate( pid, sr, service );
+            updateThread.schedule( update );
+        }
+
+    }
+
+
+    private void configure( ServiceReference sr, ManagedServiceFactory service )
+    {
+        String pid = ( String ) sr.getProperty( Constants.SERVICE_PID );
+        if ( pid != null )
+        {
+            ManagedServiceFactoryUpdate update = new ManagedServiceFactoryUpdate( pid, sr, service );
+            updateThread.schedule( update );
+        }
+
+    }
+
+
+    ConfigurationImpl getConfiguration( String pid, boolean create ) throws IOException
+    {
+        ConfigurationImpl config = getCachedConfiguration( pid );
+        if ( config != null )
+        {
+            return config;
+        }
+
+        PersistenceManager[] pmList = getPersistenceManagers();
+        for ( int i = 0; i < pmList.length; i++ )
+        {
+            if ( pmList[i].exists( pid ) )
+            {
+                Dictionary props = pmList[i].load( pid );
+                config = new ConfigurationImpl( this, pmList[i], props );
+                cacheConfiguration( config );
+                return config;
+            }
+        }
+
+        // if getting here, there is no configuration yet, optionally create new
+        return ( create ) ? createConfiguration( pid, null ) : null;
+    }
+
+
+    ConfigurationImpl createConfiguration( String pid, String factoryPid ) throws IOException
+    {
+        ConfigurationImpl config = new ConfigurationImpl( this, getPersistenceManagers()[0], pid, factoryPid );
+
+        // immediately store the configuration, yet getProperties() must still
+        // return null
+        config.store();
+
+        cacheConfiguration( config );
+
+        return config;
+    }
+
+
+    Factory getFactory( String factoryPid ) throws IOException
+    {
+        Factory factory = ( Factory ) factories.get( factoryPid );
+        if ( factory != null )
+        {
+            return factory;
+        }
+
+        PersistenceManager[] pmList = getPersistenceManagers();
+        for ( int i = 0; i < pmList.length; i++ )
+        {
+            if ( Factory.exists( pmList[i], factoryPid ) )
+            {
+                factory = Factory.load( pmList[i], factoryPid );
+                factories.put( factoryPid, factory );
+                return factory;
+            }
+        }
+
+        // if getting here, there is no configuration yet, optionally create new
+        return createFactory( factoryPid );
+    }
+
+
+    Factory createFactory( String factoryPid )
+    {
+        Factory factory = new Factory( getPersistenceManagers()[0], factoryPid );
+        factories.put( factoryPid, factory );
+        return factory;
+    }
+
+
+    private Dictionary callPlugins( ServiceReference sr, ConfigurationImpl cfg )
+    {
+        Dictionary props = cfg.getProperties();
+
+        ServiceReference[] plugins = null;
+        try
+        {
+            String pid = ( String ) sr.getProperty( Constants.SERVICE_PID );
+            String filter = "(|(!(cm.target=*))(cm.target=" + pid + "))";
+            plugins = bundleContext.getServiceReferences( ConfigurationPlugin.class.getName(), filter );
+
+        }
+        catch ( InvalidSyntaxException ise )
+        {
+            // no filter, no exception ...
+        }
+
+        // abort early if there are no plugins
+        if ( plugins == null || plugins.length == 0 )
+        {
+            return props;
+        }
+
+        // sort the plugins by their service.cmRanking
+        SortedSet pluginSet = new TreeSet( cmRankComp );
+        for ( int i = 0; plugins != null && i < plugins.length; i++ )
+        {
+            pluginSet.add( plugins[i] );
+        }
+
+        // call the plugins in order
+        for ( Iterator pi = pluginSet.iterator(); pi.hasNext(); )
+        {
+            ServiceReference pluginRef = ( ServiceReference ) pi.next();
+            ConfigurationPlugin plugin = ( ConfigurationPlugin ) bundleContext.getService( pluginRef );
+            try
+            {
+                plugin.modifyConfiguration( sr, props );
+            }
+            catch ( Throwable t )
+            {
+                log( LogService.LOG_ERROR, "Unexpected problem calling" + " configuration plugin", t );
+            }
+            finally
+            {
+                // ensure ungetting the plugin
+                bundleContext.ungetService( pluginRef );
+            }
+            cfg.setAutoProperties( props, false );
+        }
+
+        return props;
+    }
+
+
+    /**
+     * Creates a PID for the given factoryPid
+     * 
+     * @param factoryPid
+     * @return
+     */
+    private static String createPid( String factoryPid )
+    {
+        SecureRandom ng = numberGenerator;
+        if ( ng == null )
+        {
+            numberGenerator = ng = new SecureRandom();
+        }
+
+        byte[] randomBytes = new byte[16];
+        ng.nextBytes( randomBytes );
+        randomBytes[6] &= 0x0f; /* clear version */
+        randomBytes[6] |= 0x40; /* set to version 4 */
+        randomBytes[8] &= 0x3f; /* clear variant */
+        randomBytes[8] |= 0x80; /* set to IETF variant */
+
+        StringBuffer buf = new StringBuffer( factoryPid.length() + 1 + 36 );
+
+        // prefix the new pid with the factory pid
+        buf.append( factoryPid ).append( "." );
+
+        // serialize the UUID into the buffer
+        for ( int i = 0; i < randomBytes.length; i++ )
+        {
+
+            if ( i == 4 || i == 6 || i == 8 || i == 10 )
+            {
+                buf.append( '-' );
+            }
+
+            int val = randomBytes[i] & 0xff;
+            buf.append( Integer.toHexString( val >> 4 ) );
+            buf.append( Integer.toHexString( val & 0xf ) );
+        }
+
+        return buf.toString();
+    }
+
+
+    void log( int level, String message, Throwable t )
+    {
+        LogService log = ( LogService ) logTracker.getService();
+        if ( log != null )
+        {
+            log.log( configurationAdminReference, level, message, t );
+            return;
+        }
+
+        String code;
+        switch ( level )
+        {
+            case LogService.LOG_INFO:
+                code = "*INFO *";
+                break;
+
+            case LogService.LOG_WARNING:
+                code = "*WARN *";
+                break;
+
+            case LogService.LOG_ERROR:
+                code = "*ERROR*";
+                break;
+
+            case LogService.LOG_DEBUG:
+            default:
+                code = "*DEBUG*";
+        }
+
+        System.err.println( code + " " + message );
+        if ( t != null )
+        {
+            t.printStackTrace( System.err );
+        }
+    }
+
+    // ---------- inner classes ------------------------------------------------
+
+    private class ManagedServiceUpdate implements Runnable
+    {
+        private String pid;
+
+        private ServiceReference sr;
+
+        private ManagedService service;
+
+
+        ManagedServiceUpdate( String pid, ServiceReference sr, ManagedService service )
+        {
+            this.pid = pid;
+            this.sr = sr;
+            this.service = service;
+        }
+
+
+        public void run()
+        {
+            ConfigurationImpl cfg;
+            try
+            {
+                cfg = getConfiguration( pid, sr.getBundle().getLocation() );
+            }
+            catch ( IOException ioe )
+            {
+                log( LogService.LOG_ERROR, "Error loading configuration for " + pid, ioe );
+                return;
+            }
+
+            // 104.3 Ignore duplicate PIDs from other bundles and report them to
+            // the log
+            // 104.4.1 No update call back for PID already bound to another
+            // bundle location
+            String bundleLocation = sr.getBundle().getLocation();
+            if ( cfg.getBundleLocation() != null && !bundleLocation.equals( cfg.getBundleLocation() ) )
+            {
+                log( LogService.LOG_ERROR, "Cannot use configuration for " + pid + " requested by bundle "
+                    + sr.getBundle().getLocation() + " but belongs to " + cfg.getBundleLocation(), null );
+                return;
+            }
+
+            // 104.3 Report an error in the log if more than one service with
+            // the same PID asks for the configuration
+            if ( cfg.getServiceReference() != null && !sr.equals( cfg.getServiceReference() ) )
+            {
+                log( LogService.LOG_ERROR, "Configuration for " + pid + " has already been used for service "
+                    + cfg.getServiceReference() + " and will now also be given to " + sr, null );
+            }
+            else
+            {
+                // assign the configuration to the service
+                cfg.setServiceReference( sr );
+            }
+
+            // prepare the configuration for the service (call plugins)
+            Dictionary dictionary = callPlugins( sr, cfg );
+
+            // update the service with the configuration
+            try
+            {
+                service.updated( dictionary );
+            }
+            catch ( ConfigurationException ce )
+            {
+                if ( ce.getProperty() != null )
+                {
+                    log( LogService.LOG_ERROR, sr + ": Updating configuration property " + ce.getProperty()
+                        + " caused a problem: " + ce.getReason(), ce );
+                }
+                else
+                {
+                    log( LogService.LOG_ERROR, sr + ": Updating configuration caused a problem: " + ce.getReason(), ce );
+
+                }
+            }
+            catch ( Throwable t )
+            {
+                log( LogService.LOG_ERROR, sr + ": Unexpected problem updating configuration", t );
+            }
+
+            // 104.5.3 CM_UPDATED is sent asynchronously to all
+            // ConfigurationListeners
+            fireConfigurationEvent( ConfigurationEvent.CM_UPDATED, cfg );
+        }
+    }
+
+    private class ManagedServiceFactoryUpdate implements Runnable
+    {
+        private String factoryPid;
+
+        private ServiceReference sr;
+
+        private ManagedServiceFactory service;
+
+
+        ManagedServiceFactoryUpdate( String factoryPid, ServiceReference sr, ManagedServiceFactory service )
+        {
+            this.factoryPid = factoryPid;
+            this.sr = sr;
+            this.service = service;
+        }
+
+
+        public void run()
+        {
+            Factory factory;
+            try
+            {
+                factory = getFactory( factoryPid );
+            }
+            catch ( IOException ioe )
+            {
+                log( LogService.LOG_ERROR, "Cannot get factory mapping for factory PID " + factoryPid, ioe );
+                return;
+            }
+
+            String bundleLocation = sr.getBundle().getLocation();
+            if ( factory.getBundleLocation() == null )
+            {
+                // bind to the location of the service if unbound
+                factory.setBundleLocation( bundleLocation );
+            }
+            else if ( !bundleLocation.equals( factory.getBundleLocation() ) )
+            {
+                // factory PID is bound to another bundle
+                log( LogService.LOG_ERROR, "Cannot use Factory configuration for " + factoryPid
+                    + " requested by bundle " + sr.getBundle().getLocation() + " but belongs to "
+                    + factory.getBundleLocation(), null );
+                return;
+            }
+
+            Set pids = factory.getPIDs();
+
+            for ( Iterator pi = pids.iterator(); pi.hasNext(); )
+            {
+                String pid = ( String ) pi.next();
+                ConfigurationImpl cfg;
+                try
+                {
+                    cfg = getConfiguration( pid, bundleLocation );
+                }
+                catch ( IOException ioe )
+                {
+                    log( LogService.LOG_ERROR, "Error loading configuration for " + pid, ioe );
+                    continue;
+                }
+
+                // sanity check on the configuration
+                if ( cfg == null )
+                {
+                    log( LogService.LOG_ERROR, "Configuration " + pid + " referred to by factory " + factoryPid
+                        + " does not exist", null );
+                    factory.removePID( pid );
+                    factory.storeSilently();
+                    continue;
+                }
+                else if ( !factoryPid.equals( cfg.getFactoryPid() ) )
+                {
+                    log( LogService.LOG_ERROR, "Configuration " + pid + " referred to by factory " + factoryPid
+                        + " does not exist seems to belong to factory " + cfg.getFactoryPid(), null );
+                    factory.removePID( pid );
+                    factory.storeSilently();
+                    continue;
+                }
+
+                // check bundle location of configuration
+                if ( cfg.getBundleLocation() == null )
+                {
+                    // bind to the location of the service if unbound
+                    cfg.setBundleLocation( bundleLocation );
+                }
+                else if ( !bundleLocation.equals( cfg.getBundleLocation() ) )
+                {
+                    // configuration is bound to another bundle
+                    log( LogService.LOG_ERROR, "Configuration " + pid + " (factory " + factoryPid
+                        + ") belongs to bundle " + cfg.getBundleLocation() + " but was requested for bundle "
+                        + bundleLocation, null );
+                    continue;
+                }
+
+                // prepare the configuration for the service (call plugins)
+                Dictionary dictionary = callPlugins( sr, cfg );
+
+                // update the service with the configuration
+                try
+                {
+                    service.updated( pid, dictionary );
+                }
+                catch ( ConfigurationException ce )
+                {
+                    if ( ce.getProperty() != null )
+                    {
+                        log( LogService.LOG_ERROR, sr + ": Updating configuration property " + ce.getProperty()
+                            + " caused a problem: " + ce.getReason(), ce );
+                    }
+                    else
+                    {
+                        log( LogService.LOG_ERROR, sr + ": Updating configuration caused a problem: " + ce.getReason(),
+                            ce );
+
+                    }
+                }
+                catch ( Throwable t )
+                {
+                    log( LogService.LOG_ERROR, sr + ": Unexpected problem updating configuration", t );
+                }
+            }
+        }
+    }
+
+    private class UpdateConfiguration implements Runnable
+    {
+
+        private ConfigurationImpl config;
+
+
+        UpdateConfiguration( ConfigurationImpl config )
+        {
+            this.config = config;
+        }
+
+
+        public void run()
+        {
+            try
+            {
+                if ( config.getFactoryPid() == null )
+                {
+                    ServiceReference[] sr = bundleContext.getServiceReferences( ManagedService.class.getName(), "("
+                        + Constants.SERVICE_PID + "=" + config.getPid() + ")" );
+                    if ( sr != null && sr.length > 0 )
+                    {
+                        ManagedService srv = ( ManagedService ) bundleContext.getService( sr[0] );
+                        try
+                        {
+                            // bind the configuration, fail if bound to another
+                            // bundle !!
+                            // check bundle location of configuration
+                            String bundleLocation = sr[0].getBundle().getLocation();
+                            if ( config.getBundleLocation() == null )
+                            {
+                                // bind to the location of the service if
+                                // unbound
+                                config.setBundleLocation( bundleLocation );
+                            }
+                            else if ( !bundleLocation.equals( config.getBundleLocation() ) )
+                            {
+                                // configuration is bound to another bundle
+                                log( LogService.LOG_ERROR, "Configuration " + config.getPid() + " belongs to bundle "
+                                    + config.getBundleLocation() + " but was requested for bundle " + bundleLocation,
+                                    null );
+                                return;
+                            }
+
+                            srv.updated( config.getProperties() );
+                        }
+                        finally
+                        {
+                            bundleContext.ungetService( sr[0] );
+                        }
+                    }
+                }
+                else
+                {
+                    ServiceReference[] sr = bundleContext.getServiceReferences( ManagedServiceFactory.class.getName(),
+                        "(" + Constants.SERVICE_PID + "=" + config.getFactoryPid() + ")" );
+                    if ( sr != null && sr.length > 0 )
+                    {
+                        ManagedServiceFactory srv = ( ManagedServiceFactory ) bundleContext.getService( sr[0] );
+                        try
+                        {
+                            // bind the configuration, fail if bound to another
+                            // bundle !!
+                            // check bundle location of configuration
+                            String bundleLocation = sr[0].getBundle().getLocation();
+                            if ( config.getBundleLocation() == null )
+                            {
+                                // bind to the location of the service if
+                                // unbound
+                                config.setBundleLocation( bundleLocation );
+                            }
+                            else if ( !bundleLocation.equals( config.getBundleLocation() ) )
+                            {
+                                // configuration is bound to another bundle
+                                log( LogService.LOG_ERROR, "Configuration " + config.getPid() + " (factory "
+                                    + config.getFactoryPid() + ") belongs to bundle " + config.getBundleLocation()
+                                    + " but was requested for bundle " + bundleLocation, null );
+                                return;
+                            }
+
+                            srv.updated( config.getPid(), config.getProperties() );
+                        }
+                        finally
+                        {
+                            bundleContext.ungetService( sr[0] );
+                        }
+                    }
+                }
+            }
+            catch ( ConfigurationException ce )
+            {
+                if ( ce.getProperty() != null )
+                {
+                    log( LogService.LOG_ERROR, "Updating configuration property " + ce.getProperty()
+                        + " caused a problem: " + ce.getReason(), ce );
+                }
+                else
+                {
+                    log( LogService.LOG_ERROR, "Updating configuration caused a problem: " + ce.getReason(), ce );
+
+                }
+            }
+            catch ( Throwable t )
+            {
+                log( LogService.LOG_ERROR, "Unexpected problem updating configuration", t );
+            }
+
+            fireConfigurationEvent( ConfigurationEvent.CM_UPDATED, config );
+        }
+    }
+
+    private class DeleteConfiguration implements Runnable
+    {
+        private ConfigurationImpl config;
+
+
+        DeleteConfiguration( ConfigurationImpl config )
+        {
+            this.config = config;
+        }
+
+
+        public void run()
+        {
+            try
+            {
+                if ( config.getFactoryPid() == null )
+                {
+                    ServiceReference[] sr = bundleContext.getServiceReferences( ManagedService.class.getName(), "("
+                        + Constants.SERVICE_PID + "=" + config.getPid() + ")" );
+                    if ( sr != null && sr.length > 0 )
+                    {
+                        ManagedService srv = ( ManagedService ) bundleContext.getService( sr[0] );
+                        try
+                        {
+                            srv.updated( null );
+                        }
+                        finally
+                        {
+                            bundleContext.ungetService( sr[0] );
+                        }
+                    }
+                }
+                else
+                {
+                    // remove the pid from the factory
+                    Factory factory = getFactory( config.getFactoryPid() );
+                    factory.removePID( config.getPid() );
+                    factory.store();
+
+                    ServiceReference[] sr = bundleContext.getServiceReferences( ManagedServiceFactory.class.getName(),
+                        "(" + Constants.SERVICE_PID + "=" + config.getFactoryPid() + ")" );
+                    if ( sr != null && sr.length > 0 )
+                    {
+                        ManagedServiceFactory srv = ( ManagedServiceFactory ) bundleContext.getService( sr[0] );
+                        try
+                        {
+                            srv.deleted( config.getPid() );
+                        }
+                        finally
+                        {
+                            bundleContext.ungetService( sr[0] );
+                        }
+                    }
+                }
+            }
+            catch ( ConfigurationException ce )
+            {
+                if ( ce.getProperty() != null )
+                {
+                    log( LogService.LOG_ERROR, "Updating configuration property " + ce.getProperty()
+                        + " caused a problem: " + ce.getReason(), ce );
+                }
+                else
+                {
+                    log( LogService.LOG_ERROR, "Updating configuration caused a problem: " + ce.getReason(), ce );
+
+                }
+            }
+            catch ( Throwable t )
+            {
+                log( LogService.LOG_ERROR, "Unexpected problem updating configuration", t );
+            }
+
+            fireConfigurationEvent( ConfigurationEvent.CM_DELETED, config );
+        }
+    }
+
+    private class FireConfigurationEvent implements Runnable
+    {
+        private int type;
+
+        private ConfigurationImpl config;
+
+
+        FireConfigurationEvent( int type, ConfigurationImpl config )
+        {
+            this.type = type;
+            this.config = config;
+        }
+
+
+        public void run()
+        {
+            // get the listeners
+            ServiceReference[] srs = configurationListenerTracker.getServiceReferences();
+            if ( srs == null || srs.length == 0 )
+            {
+                return;
+            }
+
+            ConfigurationEvent event = new ConfigurationEvent( configurationAdminReference, type, config
+                .getFactoryPid(), config.getPid() );
+
+            for ( int i = 0; i < srs.length; i++ )
+            {
+                ConfigurationListener cl = ( ConfigurationListener ) configurationListenerTracker.getService( srs[i] );
+                try
+                {
+                    cl.configurationEvent( event );
+                }
+                catch ( Throwable t )
+                {
+                    log( LogService.LOG_ERROR, "Unexpected problem delivery configuration event to " + srs[i], t );
+                }
+            }
+        }
+    }
+
+    private abstract class AbstractManagedServiceTracker extends ServiceTracker
+    {
+        AbstractManagedServiceTracker( String className )
+        {
+            super( bundleContext, className, null );
+            open();
+        }
+
+
+        public void removedService( ServiceReference reference, Object service )
+        {
+            // check whether we can take back the configuration object
+            String pid = ( String ) reference.getProperty( Constants.SERVICE_PID );
+            if ( pid != null )
+            {
+                ConfigurationImpl cfg = getCachedConfiguration( pid );
+                if ( cfg != null && reference.equals( cfg.getServiceReference() ) )
+                {
+                    cfg.setServiceReference( null );
+                }
+            }
+
+            super.removedService( reference, service );
+        }
+    }
+
+    private class ManagedServiceTracker extends AbstractManagedServiceTracker
+    {
+        ManagedServiceTracker()
+        {
+            super( ManagedService.class.getName() );
+        }
+
+
+        public Object addingService( ServiceReference reference )
+        {
+            ManagedService service = ( ManagedService ) super.addingService( reference );
+
+            // configure the managed service
+            if ( service != null )
+            {
+                configure( reference, service );
+            }
+
+            return service;
+        }
+    }
+
+    private class ManagedServiceFactoryTracker extends AbstractManagedServiceTracker
+    {
+        ManagedServiceFactoryTracker()
+        {
+            super( ManagedServiceFactory.class.getName() );
+        }
+
+
+        public Object addingService( ServiceReference reference )
+        {
+            ManagedServiceFactory service = ( ManagedServiceFactory ) super.addingService( reference );
+
+            // configure the managed service
+            if ( service != null )
+            {
+                configure( reference, service );
+            }
+
+            return service;
+        }
+    }
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/Factory.java b/configadmin/src/main/java/org/apache/felix/cm/impl/Factory.java
new file mode 100644
index 0000000..f8e98ef
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/Factory.java
@@ -0,0 +1,185 @@
+/* 
+ * 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.cm.impl;
+
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Set;
+
+import org.apache.felix.cm.PersistenceManager;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.log.LogService;
+
+
+/**
+ * The <code>Factory</code> class is used to manage mappings between factory PIDs
+ * the configuration PID belonging to it.
+ *
+ * @author fmeschbe
+ */
+class Factory
+{
+
+    public static final String FACTORY_PID_LIST = "factory.pidList";
+
+    // the persistence manager storing this factory mapping
+    private PersistenceManager persistenceManager;
+    
+    // the factory PID of this factory
+    private String factoryPid;
+    
+    // the bundle location to which factory PID mapping is bound 
+    private String bundleLocation;
+    
+    // the set of configuration PIDs belonging to this factory
+    private Set pids;
+
+
+    static boolean exists( PersistenceManager persistenceManager, String factoryPid )
+    {
+        return persistenceManager.exists( factoryPidToIdentifier( factoryPid ) );
+    }
+
+
+    static Factory load( PersistenceManager persistenceManager, String factoryPid ) throws IOException
+    {
+        Dictionary dict = persistenceManager.load( factoryPidToIdentifier( factoryPid ) );
+        return new Factory( persistenceManager, factoryPid, dict );
+    }
+
+
+    private static String factoryPidToIdentifier( String factoryPid )
+    {
+        return factoryPid + ".factory";
+    }
+
+
+    Factory( PersistenceManager persistenceManager, String factoryPid )
+    {
+        this.persistenceManager = persistenceManager;
+        this.factoryPid = factoryPid;
+        this.pids = new HashSet();
+    }
+
+
+    Factory( PersistenceManager persistenceManager, String factoryPid, Dictionary props )
+    {
+        this( persistenceManager, factoryPid );
+
+        // set bundle location
+        setBundleLocation( ( String ) props.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION ) );
+
+        // set pids
+        String[] pidList = ( String[] ) props.get( FACTORY_PID_LIST );
+        if ( pidList != null )
+        {
+            for ( int i = 0; i < pidList.length; i++ )
+            {
+                addPID( pidList[i] );
+            }
+        }
+    }
+
+
+    PersistenceManager getPersistenceManager()
+    {
+        return persistenceManager;
+    }
+
+
+    String getFactoryPid()
+    {
+        return factoryPid;
+    }
+
+
+    String getBundleLocation()
+    {
+        return bundleLocation;
+    }
+
+
+    void setBundleLocation( String bundleLocation )
+    {
+        this.bundleLocation = bundleLocation;
+        
+        // 104.15.2.8 The bundle location will be set persistently
+        storeSilently();
+    }
+
+
+    Set getPIDs()
+    {
+        return new HashSet( pids );
+    }
+
+
+    boolean addPID( String pid )
+    {
+        return pids.add( pid );
+    }
+
+
+    boolean removePID( String pid )
+    {
+        return pids.remove( pid );
+    }
+
+
+    void store() throws IOException
+    {
+        Hashtable props = new Hashtable();
+
+        if ( bundleLocation != null )
+        {
+            props.put( ConfigurationAdmin.SERVICE_BUNDLELOCATION, getBundleLocation() );
+        }
+
+        if ( !pids.isEmpty() )
+        {
+            props.put( FACTORY_PID_LIST, pids.toArray( new String[pids.size()] ) );
+        }
+
+        String id = factoryPidToIdentifier( getFactoryPid() );
+        if ( props.isEmpty() )
+        {
+            persistenceManager.delete( id );
+        }
+        else
+        {
+            persistenceManager.store( id, props );
+        }
+    }
+    
+    void storeSilently()
+    {
+        try
+        {
+            store();
+        }
+        catch ( IOException ioe )
+        {
+            // should actually log this problem
+            // configurationManager.log( LogService.LOG_ERROR, "Persisting new bundle location failed", ioe );
+        }
+    }
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/PersistenceManagerProxy.java b/configadmin/src/main/java/org/apache/felix/cm/impl/PersistenceManagerProxy.java
new file mode 100644
index 0000000..24ee315
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/PersistenceManagerProxy.java
@@ -0,0 +1,120 @@
+/* 
+ * 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.cm.impl;
+
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Enumeration;
+
+import org.apache.felix.cm.PersistenceManager;
+
+
+/**
+ * The <code>PersistenceManagerProxy</code> TODO
+ *
+ * @author fmeschbe
+ */
+class PersistenceManagerProxy implements PersistenceManager
+{
+
+    private PersistenceManager delegatee;
+
+
+    PersistenceManagerProxy( PersistenceManager delegatee )
+    {
+        setPersistenceManager( delegatee );
+    }
+
+
+    /**
+     * @param pid
+     * @throws IOException
+     * @see org.apache.felix.cm.PersistenceManager#delete(java.lang.String)
+     */
+    public void delete( String pid ) throws IOException
+    {
+        checkDelegatee();
+        delegatee.delete( pid );
+    }
+
+
+    /**
+     * @param pid
+     * @return
+     * @see org.apache.felix.cm.PersistenceManager#exists(java.lang.String)
+     */
+    public boolean exists( String pid )
+    {
+        return delegatee != null && delegatee.exists( pid );
+    }
+
+
+    /**
+     * @return
+     * @throws IOException
+     * @see org.apache.felix.cm.PersistenceManager#getDictionaries()
+     */
+    public Enumeration getDictionaries() throws IOException
+    {
+        checkDelegatee();
+        return delegatee.getDictionaries();
+    }
+
+
+    /**
+     * @param pid
+     * @return
+     * @throws IOException
+     * @see org.apache.felix.cm.PersistenceManager#load(java.lang.String)
+     */
+    public Dictionary load( String pid ) throws IOException
+    {
+        checkDelegatee();
+        return delegatee.load( pid );
+    }
+
+
+    /**
+     * @param pid
+     * @param properties
+     * @throws IOException
+     * @see org.apache.felix.cm.PersistenceManager#store(java.lang.String, java.util.Dictionary)
+     */
+    public void store( String pid, Dictionary properties ) throws IOException
+    {
+        checkDelegatee();
+        delegatee.store( pid, properties );
+    }
+
+
+    void setPersistenceManager( PersistenceManager delegatee )
+    {
+        this.delegatee = delegatee;
+    }
+
+
+    void checkDelegatee() throws IOException
+    {
+        if ( delegatee == null )
+        {
+            throw new IOException( "PersistenceManager not valid" );
+        }
+    }
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/RankingComparator.java b/configadmin/src/main/java/org/apache/felix/cm/impl/RankingComparator.java
new file mode 100644
index 0000000..657154a
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/RankingComparator.java
@@ -0,0 +1,98 @@
+/* 
+ * 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.cm.impl;
+
+
+import java.util.Comparator;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ConfigurationPlugin;
+
+
+/**
+ * The <code>RankingComparator</code> TODO
+ *
+ * @author fmeschbe
+ */
+public class RankingComparator implements Comparator
+{
+
+    private final boolean naturalOrder;
+    private final String rankProperty;
+
+
+    public RankingComparator(boolean naturalOrder)
+    {
+        this( naturalOrder, Constants.SERVICE_RANKING );
+    }
+
+
+    public RankingComparator( boolean naturalOrder, String rankProperty )
+    {
+        this.naturalOrder = naturalOrder;
+        this.rankProperty = rankProperty;
+    }
+
+
+    public int compare( Object obj1, Object obj2 )
+    {
+        if ( obj1.equals( obj2 ) )
+        {
+            return 0;
+        }
+
+        int rank1 = getInt( ( ServiceReference ) obj1, rankProperty );
+        int rank2 = getInt( ( ServiceReference ) obj2, rankProperty );
+
+        // use service id, if rankings are equal
+        if ( rank1 == rank2 )
+        {
+            rank1 = getInt( ( ServiceReference ) obj1, Constants.SERVICE_ID );
+            rank2 = getInt( ( ServiceReference ) obj2, Constants.SERVICE_ID );
+        }
+
+        if ( rank1 == rank2 )
+        {
+            return 0;
+        }
+        else if ( naturalOrder && rank1 > rank2)
+        {
+            return 1;
+        }
+        else
+        {
+            return -1;
+        }
+    }
+
+
+    private int getInt( ServiceReference sr, String property )
+    {
+        Object rankObj = sr.getProperty( property );
+        if ( rankObj instanceof Integer )
+        {
+            return ( ( Integer ) rankObj ).intValue();
+        }
+
+        // null or not an integer
+        return 0;
+    }
+
+}
diff --git a/configadmin/src/main/java/org/apache/felix/cm/impl/UpdateThread.java b/configadmin/src/main/java/org/apache/felix/cm/impl/UpdateThread.java
new file mode 100644
index 0000000..9a5f47d
--- /dev/null
+++ b/configadmin/src/main/java/org/apache/felix/cm/impl/UpdateThread.java
@@ -0,0 +1,119 @@
+/* 
+ * 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.cm.impl;
+
+
+import java.util.LinkedList;
+
+import org.osgi.service.log.LogService;
+
+
+/**
+ * The <code>UpdateThread</code> is the thread used to update managed services
+ * and managed service factories as well as to send configuration events.
+ *
+ * @author fmeschbe
+ */
+public class UpdateThread extends Thread
+{
+
+    // the configuration manager on whose behalf this thread is started
+    // (this is mainly used for logging)
+    private ConfigurationManager configurationManager;
+    
+    // the queue of Runnable instances  to be run
+    private LinkedList updateTasks;
+
+
+    public UpdateThread( ConfigurationManager configurationManager )
+    {
+        super( "Configuration Updater" );
+
+        this.configurationManager = configurationManager;
+        this.updateTasks = new LinkedList();
+    }
+
+
+    // waits on Runnable instances coming into the queue. As instances come
+    // in, this method calls the Runnable.run method, logs any exception
+    // happening and keeps on waiting for the next Runnable. If the Runnable
+    // taken from the queue is this thread instance itself, the thread
+    // terminates.
+    public void run()
+    {
+        for ( ;; )
+        {
+            Runnable task;
+            synchronized ( updateTasks )
+            {
+                while ( updateTasks.isEmpty() )
+                {
+                    try
+                    {
+                        updateTasks.wait();
+                    }
+                    catch ( InterruptedException ie )
+                    {
+                        // don't care
+                    }
+                }
+
+                task = ( Runnable ) updateTasks.removeFirst();
+            }
+
+            // return if the task is this thread itself
+            if ( task == this )
+            {
+                return;
+            }
+
+            // otherwise execute the task, log any issues
+            try
+            {
+                task.run();
+            }
+            catch ( Throwable t )
+            {
+                configurationManager.log( LogService.LOG_ERROR, "Unexpected problem executing task", t );
+            }
+        }
+    }
+
+
+    // cause this thread to terminate by adding this thread to the end
+    // of the queue
+    void terminate()
+    {
+        schedule( this );
+    }
+
+
+    // queue the given runnable to be run as soon as possible
+    void schedule( Runnable update )
+    {
+        synchronized ( updateTasks )
+        {
+            // append to the task queue
+            updateTasks.add( update );
+            
+            // notify the waiting thread
+            updateTasks.notifyAll();
+        }
+    }
+}
diff --git a/configadmin/src/test/java/org/apache/felix/cm/MockBundleContext.java b/configadmin/src/test/java/org/apache/felix/cm/MockBundleContext.java
new file mode 100644
index 0000000..1b1dbff
--- /dev/null
+++ b/configadmin/src/test/java/org/apache/felix/cm/MockBundleContext.java
@@ -0,0 +1,260 @@
+/*
+ * $Url: $
+ * $Id$
+ *
+ * Copyright 1997-2005 Day Management AG
+ * Barfuesserplatz 6, 4001 Basel, Switzerland
+ * All Rights Reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Day Management AG, ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Day.
+ */
+package org.apache.felix.cm;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Dictionary;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * The <code>MockBundleContext</code> TODO
+ *
+ * @author fmeschbe
+ * @version $Rev:$, $Date:$
+ */
+public class MockBundleContext implements BundleContext
+{
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#addBundleListener(org.osgi.framework.BundleListener)
+     */
+    public void addBundleListener( BundleListener arg0 )
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#addFrameworkListener(org.osgi.framework.FrameworkListener)
+     */
+    public void addFrameworkListener( FrameworkListener arg0 )
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#addServiceListener(org.osgi.framework.ServiceListener)
+     */
+    public void addServiceListener( ServiceListener arg0 )
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#addServiceListener(org.osgi.framework.ServiceListener, java.lang.String)
+     */
+    public void addServiceListener( ServiceListener arg0, String arg1 ) throws InvalidSyntaxException
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#createFilter(java.lang.String)
+     */
+    public Filter createFilter( String arg0 ) throws InvalidSyntaxException
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#getAllServiceReferences(java.lang.String, java.lang.String)
+     */
+    public ServiceReference[] getAllServiceReferences( String arg0, String arg1 ) throws InvalidSyntaxException
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#getBundle()
+     */
+    public Bundle getBundle()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#getBundle(long)
+     */
+    public Bundle getBundle( long arg0 )
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#getBundles()
+     */
+    public Bundle[] getBundles()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#getDataFile(java.lang.String)
+     */
+    public File getDataFile( String arg0 )
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#getProperty(java.lang.String)
+     */
+    public String getProperty( String arg0 )
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#getService(org.osgi.framework.ServiceReference)
+     */
+    public Object getService( ServiceReference arg0 )
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#getServiceReference(java.lang.String)
+     */
+    public ServiceReference getServiceReference( String arg0 )
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#getServiceReferences(java.lang.String, java.lang.String)
+     */
+    public ServiceReference[] getServiceReferences( String arg0, String arg1 ) throws InvalidSyntaxException
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#installBundle(java.lang.String)
+     */
+    public Bundle installBundle( String arg0 ) throws BundleException
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#installBundle(java.lang.String, java.io.InputStream)
+     */
+    public Bundle installBundle( String arg0, InputStream arg1 ) throws BundleException
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#registerService(java.lang.String[], java.lang.Object, java.util.Dictionary)
+     */
+    public ServiceRegistration registerService( String[] arg0, Object arg1, Dictionary arg2 )
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#registerService(java.lang.String, java.lang.Object, java.util.Dictionary)
+     */
+    public ServiceRegistration registerService( String arg0, Object arg1, Dictionary arg2 )
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#removeBundleListener(org.osgi.framework.BundleListener)
+     */
+    public void removeBundleListener( BundleListener arg0 )
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#removeFrameworkListener(org.osgi.framework.FrameworkListener)
+     */
+    public void removeFrameworkListener( FrameworkListener arg0 )
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#removeServiceListener(org.osgi.framework.ServiceListener)
+     */
+    public void removeServiceListener( ServiceListener arg0 )
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.osgi.framework.BundleContext#ungetService(org.osgi.framework.ServiceReference)
+     */
+    public boolean ungetService( ServiceReference arg0 )
+    {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+}
diff --git a/configadmin/src/test/java/org/apache/felix/cm/file/FileNameTest.java b/configadmin/src/test/java/org/apache/felix/cm/file/FileNameTest.java
new file mode 100644
index 0000000..f010a50
--- /dev/null
+++ b/configadmin/src/test/java/org/apache/felix/cm/file/FileNameTest.java
@@ -0,0 +1,104 @@
+/*
+ * $Url: $
+ * $Id$
+ *
+ * Copyright 1997-2005 Day Management AG
+ * Barfuesserplatz 6, 4001 Basel, Switzerland
+ * All Rights Reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Day Management AG, ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Day.
+ */
+package org.apache.felix.cm.file;
+
+
+import java.io.File;
+import java.util.BitSet;
+
+import junit.framework.TestCase;
+
+
+public class FileNameTest extends TestCase
+{
+
+    public void testPidPlain()
+    {
+        assertEquals( "plain", encodePath( "plain" ) );
+        assertEquals( "plain" + File.separatorChar + "path", encodePath( "plain.path" ) );
+        assertEquals( "encod%00e8", encodePath( "encodè" ) );
+        assertEquals( "encod%00e8" + File.separatorChar + "path", encodePath( "encodè/path" ) );
+        assertEquals( "encode" + File.separatorChar + "%1234" + File.separatorChar + "path", encodePath( "encode/\u1234/path" ) );
+        assertEquals( "encode" + File.separatorChar + " %0025 " + File.separatorChar + "path", encodePath( "encode/ % /path" ) );
+    }
+
+    private static final BitSet VALID_PATH_CHARS;
+
+    static
+    {
+        VALID_PATH_CHARS = new BitSet();
+
+        for ( int i = 'a'; i <= 'z'; i++ )
+        {
+            VALID_PATH_CHARS.set( i );
+        }
+        for ( int i = 'A'; i <= 'Z'; i++ )
+        {
+            VALID_PATH_CHARS.set( i );
+        }
+        for ( int i = '0'; i <= '9'; i++ )
+        {
+            VALID_PATH_CHARS.set( i );
+        }
+        VALID_PATH_CHARS.set( File.separatorChar );
+        VALID_PATH_CHARS.set( ' ' );
+        VALID_PATH_CHARS.set( '-' );
+        VALID_PATH_CHARS.set( '_' );
+    }
+
+
+    private String encodePath( String pid )
+    {
+        // replace dots by File.separatorChar
+        pid = pid.replace( '.', File.separatorChar );
+
+        // replace slash by File.separatorChar if different
+        if (File.separatorChar != '/') {
+            pid = pid.replace( '/', File.separatorChar );
+        }
+
+        // scan for first non-valid character (if any)
+        int first = 0;
+        while ( first < pid.length() && VALID_PATH_CHARS.get( pid.charAt( first ) ) )
+        {
+            first++;
+        }
+
+        // check whether we exhausted
+        if ( first < pid.length() )
+        {
+            StringBuffer buf = new StringBuffer( pid.substring( 0, first ) );
+
+            for ( int i = first; i < pid.length(); i++ )
+            {
+                char c = pid.charAt( i );
+                if ( VALID_PATH_CHARS.get( c ) )
+                {
+                    buf.append( c );
+                }
+                else
+                {
+                    String val = "000" + Integer.toHexString( c );
+                    buf.append( '%' );
+                    buf.append( val.substring( val.length() - 4 ) );
+                }
+            }
+            
+            return buf.toString();
+        }
+        
+        return pid;
+    }
+}
diff --git a/configadmin/src/test/java/org/apache/felix/cm/file/FilePersistenceManagerTest.java b/configadmin/src/test/java/org/apache/felix/cm/file/FilePersistenceManagerTest.java
new file mode 100644
index 0000000..6effa5a
--- /dev/null
+++ b/configadmin/src/test/java/org/apache/felix/cm/file/FilePersistenceManagerTest.java
@@ -0,0 +1,177 @@
+/*
+ * $Url: $
+ * $Id$
+ *
+ * Copyright 1997-2005 Day Management AG
+ * Barfuesserplatz 6, 4001 Basel, Switzerland
+ * All Rights Reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Day Management AG, ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Day.
+ */
+package org.apache.felix.cm.file;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import junit.framework.TestCase;
+
+
+/**
+ * The <code>FilePersistenceManagerTest</code> TODO
+ *
+ * @author fmeschbe
+ * @version $Rev:$, $Date:$
+ */
+public class FilePersistenceManagerTest extends TestCase
+{
+    private File file = new File( System.getProperty( "java.io.tmpdir" ), "config" );
+
+    private FilePersistenceManager fpm;
+
+
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+
+        fpm = new FilePersistenceManager( file.getAbsolutePath() );
+    }
+
+
+    protected void tearDown() throws Exception
+    {
+        File[] children = file.listFiles();
+        for ( int i = 0; children != null && i < children.length; i++ )
+        {
+            children[i].delete();
+        }
+        file.delete();
+
+        super.tearDown();
+    }
+
+
+    public void testCreateDir()
+    {
+        assertTrue( file.isDirectory() );
+    }
+
+
+    public void testSimple() throws IOException
+    {
+        check( "String", "String Value" );
+        check( "Integer", new Integer( 2 ) );
+        check( "Long", new Long( 2 ) );
+        check( "Float", new Float( 2 ) );
+        check( "Double", new Double( 2 ) );
+        check( "Byte", new Byte( ( byte ) 2 ) );
+        check( "Short", new Short( ( short ) 2 ) );
+        check( "Character", new Character( 'a' ) );
+        check( "Boolean", Boolean.TRUE );
+    }
+
+
+    public void testQuoting() throws IOException
+    {
+        check( "QuotingSeparators", "\\()[]{}.,=" );
+        check( "QuotingWellKnown", "BSP:\b, TAB:\t, LF:\n, FF:\f, CR:\r" );
+        check( "QuotingControl", new String( new char[]
+            { 5, 10, 32, 64 } ) );
+    }
+
+
+    public void testArray() throws IOException
+    {
+        check( "StringArray", new String[]
+            { "one", "two", "three" } );
+        check( "IntArray", new int[]
+            { 0, 1, 2 } );
+        check( "IntegerArray", new Integer[]
+            { new Integer( 0 ), new Integer( 1 ), new Integer( 2 ) } );
+    }
+
+
+    public void testVector() throws IOException
+    {
+        check( "StringVector", new Vector( Arrays.asList( new String[]
+            { "one", "two", "three" } ) ) );
+        check( "IntegerVector", new Vector( Arrays.asList( new Integer[]
+            { new Integer( 0 ), new Integer( 1 ), new Integer( 2 ) } ) ) );
+    }
+
+
+    public void testMultiValue() throws IOException
+    {
+        Dictionary props = new Hashtable();
+        props.put( "String", "String Value" );
+        props.put( "Integer", new Integer( 2 ) );
+        props.put( "Long", new Long( 2 ) );
+        props.put( "Float", new Float( 2 ) );
+        props.put( "Double", new Double( 2 ) );
+        props.put( "Byte", new Byte( ( byte ) 2 ) );
+        props.put( "Short", new Short( ( short ) 2 ) );
+        props.put( "Character", new Character( 'a' ) );
+        props.put( "Boolean", Boolean.TRUE );
+        props.put( "Array", new boolean[]
+            { true, false } );
+
+        check( "MultiValue", props );
+    }
+
+
+    private void check( String name, Object value ) throws IOException
+    {
+        Dictionary props = new Hashtable();
+        props.put( name, value );
+
+        check( name, props );
+    }
+
+
+    private void check( String pid, Dictionary props ) throws IOException
+    {
+        fpm.store( pid, props );
+
+        assertTrue( new File( file, pid + ".config" ).exists() );
+
+        Dictionary loaded = fpm.load( pid );
+        assertNotNull( loaded );
+        assertEquals( props.size(), loaded.size() );
+
+        for ( Enumeration pe = props.keys(); pe.hasMoreElements(); )
+        {
+            String key = ( String ) pe.nextElement();
+            checkValues( props.get( key ), loaded.get( key ) );
+        }
+    }
+
+
+    private void checkValues( Object value1, Object value2 )
+    {
+        assertNotNull( value2 );
+        if ( value1.getClass().isArray() )
+        {
+            assertTrue( value2.getClass().isArray() );
+            assertEquals( value1.getClass().getComponentType(), value2.getClass().getComponentType() );
+            assertEquals( Array.getLength( value1 ), Array.getLength( value2 ) );
+            for ( int i = 0; i < Array.getLength( value1 ); i++ )
+            {
+                assertEquals( Array.get( value1, i ), Array.get( value2, i ) );
+            }
+        }
+        else
+        {
+            assertEquals( value1, value2 );
+        }
+    }
+}