move preferences implementation to trunk (this time with correct url)

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@560958 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/prefs/pom.xml b/prefs/pom.xml
new file mode 100644
index 0000000..78427f7
--- /dev/null
+++ b/prefs/pom.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>felix</artifactId>
+        <version>1.0.0</version>
+    </parent>
+
+    <artifactId>org.apache.felix.preferences</artifactId>
+    <packaging>bundle</packaging>
+    <version>0.1.0-SNAPSHOT</version>
+
+    <name>Apache Felix Prefrences Service</name>
+    <description>
+        Implementation of the OSGi Preferences Service Specification 1.1
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>${pom.groupId}</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+        <dependency>
+            <groupId>${pom.groupId}</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <version>0.9.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.3</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <version>1.0.0</version>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-Category>osgi</Bundle-Category>
+                        <Export-Package>
+                            org.apache.felix.sandbox.preferences,
+                            org.osgi.service.prefs;version=1.1
+                        </Export-Package>
+                        <Private-Package>
+                            org.apache.felix.sandbox.preferences.*,
+                            org.apache.commons.codec.binary.*
+                        </Private-Package>
+                        <Bundle-Activator>
+                            org.apache.felix.sandbox.preferences.impl.PreferencesManager
+                        </Bundle-Activator>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/prefs/src/main/java/org/apache/felix/sandbox/preferences/BackingStore.java b/prefs/src/main/java/org/apache/felix/sandbox/preferences/BackingStore.java
new file mode 100644
index 0000000..35e356b
--- /dev/null
+++ b/prefs/src/main/java/org/apache/felix/sandbox/preferences/BackingStore.java
@@ -0,0 +1,76 @@
+/*
+ * 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.sandbox.preferences;
+
+import org.osgi.service.prefs.BackingStoreException;
+
+/**
+ * The BackingStore for the preferences.
+ *
+ * This interface allows for different implementation strategies.
+ */
+public interface BackingStore {
+
+    /**
+     * Store the current preferences and its children in the backing
+     * store.
+     * The store should check, if the preferences have changed,
+     * it should also check all children.
+     * @param prefs The preferences.
+     * @throws BackingStoreException
+     */
+    void store(PreferencesImpl prefs) throws BackingStoreException;
+
+    /**
+     * Update the current preferences and its children from the
+     * backing store.
+     */
+    void update(PreferencesImpl prefs) throws BackingStoreException;
+
+    /**
+     * Return all bundle ids for which preferences are stored..
+     * @return Return an array of bundle ids or an empty array.
+     */
+    Long[] availableBundles();
+
+    /**
+     * Remove all preferences stored for this bundle.
+     * @param bundleId The bundle id.
+     * @throws BackingStoreException
+     */
+    void remove(Long bundleId) throws BackingStoreException;
+
+    /**
+     * Load the preferences for the given description.
+     * @param manager The backing store manager which should be passed to new preferences implementations.
+     * @param desc
+     * @return A new preferences object or null if it's not available in the backing store.
+     * @throws BackingStoreException
+     */
+    PreferencesImpl load(BackingStoreManager manager, PreferencesDescription desc) throws BackingStoreException;
+
+    /**
+     * Load all preferences for this bundle.
+     * @param manager The backing store manager which should be passed to new preferences implementations.
+     * @param bundleId The bundle id.
+     * @return An array with the preferences or an empty array.
+     * @throws BackingStoreException
+     */
+    PreferencesImpl[] loadAll(BackingStoreManager manager, Long bundleId) throws BackingStoreException;
+}
diff --git a/prefs/src/main/java/org/apache/felix/sandbox/preferences/BackingStoreManager.java b/prefs/src/main/java/org/apache/felix/sandbox/preferences/BackingStoreManager.java
new file mode 100644
index 0000000..aea89e4
--- /dev/null
+++ b/prefs/src/main/java/org/apache/felix/sandbox/preferences/BackingStoreManager.java
@@ -0,0 +1,31 @@
+/*
+ * 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.sandbox.preferences;
+
+
+/**
+ * Get the backing store.
+ */
+public interface BackingStoreManager {
+
+    /**
+     * Return the current backing store.
+     */
+    BackingStore getStore();
+}
diff --git a/prefs/src/main/java/org/apache/felix/sandbox/preferences/ChangeSet.java b/prefs/src/main/java/org/apache/felix/sandbox/preferences/ChangeSet.java
new file mode 100644
index 0000000..647626d
--- /dev/null
+++ b/prefs/src/main/java/org/apache/felix/sandbox/preferences/ChangeSet.java
@@ -0,0 +1,150 @@
+/*
+ * 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.sandbox.preferences;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class keeps track of the changes to a preferences node.
+ */
+public class ChangeSet {
+
+    /** Do we have changes at all? */
+    protected boolean hasChanges = false;
+
+    /** A set of changed/added properties. */
+    protected final Set changedProperties = new HashSet();
+
+    /** A set of removed properties. */
+    protected final Set removedProperties = new HashSet();
+
+    /** A set of added children. */
+    protected final Set addedChildren = new HashSet();
+
+    /** A set of removed children. */
+    protected final Set removedChildren = new HashSet();
+
+    /**
+     * Do we have changes?
+     * @return
+     */
+    public boolean hasChanges() {
+        return this.hasChanges;
+    }
+
+    /**
+     * Inform that a property has been added/changed.
+     * @param name The name of the property.
+     */
+    public void propertyChanged(String name) {
+        this.hasChanges = true;
+        this.removedProperties.remove(name);
+        this.changedProperties.add(name);
+    }
+
+    /**
+     * Inform that a property has removed.
+     * @param name The name of the property.
+     */
+    public void propertyRemoved(String name) {
+        this.hasChanges = true;
+        this.changedProperties.remove(name);
+        this.removedProperties.add(name);
+    }
+
+    /**
+     * Inform that a child has been added.
+     * @param name The name of the child.
+     */
+    public void childAdded(String name) {
+        this.hasChanges = true;
+        this.removedChildren.remove(name);
+        this.addedChildren.add(name);
+    }
+
+    /**
+     * Inform that a child has been removed.
+     * @param name The name of the child.
+     */
+    public void childRemoved(String name) {
+        this.hasChanges = true;
+        this.addedChildren.remove(name);
+        this.removedChildren.add(name);
+    }
+
+    /**
+     * Reset state to unchanged.
+     */
+    public void clear() {
+        this.hasChanges = false;
+        this.removedChildren.clear();
+        this.removedProperties.clear();
+        this.addedChildren.clear();
+        this.changedProperties.clear();
+    }
+
+    /**
+     * Import the changes from the other change set.
+     * @param other
+     */
+    public void importChanges(ChangeSet other) {
+        if (other.hasChanges) {
+            this.hasChanges = true;
+            this.addedChildren.addAll(other.addedChildren);
+            this.removedChildren.addAll(other.removedChildren);
+            this.changedProperties.addAll(other.changedProperties);
+            this.removedProperties.addAll(other.removedProperties);
+        }
+    }
+
+    /**
+     * Return a collection with the changed property names.
+     * @return A collection.
+     */
+    public Collection getChangedProperties() {
+        return Collections.unmodifiableCollection(this.changedProperties);
+    }
+
+    /**
+     * Return a collection with the removed property names.
+     * @return A collection.
+     */
+    public Collection getRemovedProperties() {
+        return Collections.unmodifiableCollection(this.removedProperties);
+    }
+
+    /**
+     * Return a collection with the added children names.
+     * @return A collection.
+     */
+    public Collection getAddedChildren() {
+        return Collections.unmodifiableCollection(this.addedChildren);
+    }
+
+    /**
+     * Return a collection with the removed children names.
+     * @return A collection.
+     */
+    public Collection getRemovedChildren() {
+        return Collections.unmodifiableCollection(this.removedChildren);
+    }
+}
diff --git a/prefs/src/main/java/org/apache/felix/sandbox/preferences/PreferencesDescription.java b/prefs/src/main/java/org/apache/felix/sandbox/preferences/PreferencesDescription.java
new file mode 100644
index 0000000..7cb1531
--- /dev/null
+++ b/prefs/src/main/java/org/apache/felix/sandbox/preferences/PreferencesDescription.java
@@ -0,0 +1,46 @@
+/*
+ * 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.sandbox.preferences;
+
+/**
+ * A preferences description identifies a preferences try.
+ * It is a unique identifier consisting of:
+ * - the bundle id
+ * - the preferences name which is not the node name but either
+ *   null for the system preferences or the user name.
+ */
+public class PreferencesDescription {
+
+    protected final Long bundleId;
+
+    protected final String identifier;
+
+    public PreferencesDescription(Long id, String identifier) {
+        this.bundleId = id;
+        this.identifier = identifier;
+    }
+
+    public Long getBundleId() {
+        return this.bundleId;
+    }
+
+    public String getIdentifier() {
+        return this.identifier;
+    }
+}
diff --git a/prefs/src/main/java/org/apache/felix/sandbox/preferences/PreferencesImpl.java b/prefs/src/main/java/org/apache/felix/sandbox/preferences/PreferencesImpl.java
new file mode 100644
index 0000000..48a5192
--- /dev/null
+++ b/prefs/src/main/java/org/apache/felix/sandbox/preferences/PreferencesImpl.java
@@ -0,0 +1,654 @@
+/*
+ * 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.sandbox.preferences;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.codec.binary.Base64;
+import org.osgi.service.prefs.BackingStoreException;
+import org.osgi.service.prefs.Preferences;
+
+/**
+ * This is an implementation of the preferences.
+ *
+ * The access to the preferences is synchronized on the instance
+ * by making (nearly) all public methods synchronized. This avoids the
+ * heavy management of a separate read/write lock. Such a lock
+ * is too heavy for the simple operations preferences support.
+ * The various getXX and putXX methods are not synchronized as they
+ * all use the get/put methods which are synchronized.
+ */
+public class PreferencesImpl implements Preferences {
+
+    /** The properties. */
+    protected final Map properties = new HashMap();
+
+    /** Has this node been removed? */
+    protected boolean valid = true;
+
+    /** The parent. */
+    protected final PreferencesImpl parent;
+
+    /** The child nodes. */
+    protected final Map children = new HashMap();
+
+    /** The name of the properties. */
+    protected final String name;
+
+    /** The description for this preferences. */
+    protected final PreferencesDescription description;
+
+    /** The backing store manager. */
+    protected final BackingStoreManager storeManager;
+
+    /** The change set keeps track of all changes. */
+    protected final ChangeSet changeSet = new ChangeSet();
+
+    /**
+     * Construct the root node of the tree.
+     * @param d The unique description.
+     * @param storeManager The backing store.
+     */
+    public PreferencesImpl(PreferencesDescription d, BackingStoreManager storeManager) {
+        this.parent = null;
+        this.name = "";
+        this.description = d;
+        this.storeManager = storeManager;
+    }
+
+    /**
+     * Construct a child node.
+     * @param p The parent node.
+     * @param name The node name
+     */
+    public PreferencesImpl(PreferencesImpl p, String name) {
+        this.parent = p;
+        this.name = name;
+        this.description = p.description;
+        this.storeManager = p.storeManager;
+    }
+
+    /**
+     * Return the change set.
+     */
+    public ChangeSet getChangeSet() {
+        return this.changeSet;
+    }
+
+    /**
+     * Return the preferences description.
+     * @return
+     */
+    public PreferencesDescription getDescription() {
+        return this.description;
+    }
+
+    /**
+     * Get the root preferences.
+     */
+    public PreferencesImpl getRoot() {
+        PreferencesImpl root = this;
+        while ( root.parent != null ) {
+            root = root.parent;
+        }
+        return root;
+    }
+
+    /**
+     * Return all children or an empty collection.
+     * @return
+     */
+    public Collection getChildren() {
+        return this.children.values();
+    }
+
+    /**
+     * Return the properties set.
+     */
+    public Map getProperties() {
+        return this.properties;
+    }
+
+    /**
+     * Return the backing store manager.
+     */
+    public BackingStoreManager getBackingStoreManager() {
+        return this.storeManager;
+    }
+
+    /**
+     * Check if this node is still valid.
+     * It gets invalid if it has been removed.
+     */
+    protected void checkValidity() throws IllegalStateException {
+        if ( !this.valid ) {
+            throw new IllegalStateException("The preferences node has been removed.");
+        }
+    }
+
+    /**
+     * The key is not allowed to be null.
+     */
+    protected void checkKey(String key) throws NullPointerException {
+        if ( key == null ) {
+            throw new NullPointerException("Key must not be null.");
+        }
+    }
+
+    /**
+     * The value is not allowed to be null.
+     */
+    protected void checkValue(Object value) throws NullPointerException {
+        if ( value == null ) {
+            throw new NullPointerException("Value must not be null.");
+        }
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#put(java.lang.String, java.lang.String)
+     */
+    public synchronized void put(String key, String value) {
+        this.checkKey(key);
+        this.checkValue(value);
+
+        this.checkValidity();
+
+        this.properties.put(key, value);
+        this.changeSet.propertyChanged(key);
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#get(java.lang.String, java.lang.String)
+     */
+    public synchronized String get(String key, String def) {
+        this.checkValidity();
+        String value = (String) this.properties.get(key);
+        if ( value == null ) {
+            value = def;
+        }
+        return value;
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#remove(java.lang.String)
+     */
+    public synchronized void remove(String key) {
+        this.checkKey(key);
+        this.checkValidity();
+
+        this.properties.remove(key);
+        this.changeSet.propertyRemoved(key);
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#clear()
+     */
+    public synchronized void clear() throws BackingStoreException {
+        this.checkValidity();
+
+        final Iterator i = this.properties.keySet().iterator();
+        while ( i.hasNext() ) {
+            final String key = (String)i.next();
+            this.changeSet.propertyRemoved(key);
+        }
+        this.properties.clear();
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#putInt(java.lang.String, int)
+     */
+    public void putInt(String key, int value) {
+        this.put(key, String.valueOf(value));
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#getInt(java.lang.String, int)
+     */
+    public int getInt(String key, int def) {
+        int result = def;
+        final String value = this.get(key, null);
+        if ( value != null ) {
+            try {
+                result = Integer.parseInt(value);
+            } catch (NumberFormatException ignore) {
+                // return the default value
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#putLong(java.lang.String, long)
+     */
+    public void putLong(String key, long value) {
+        this.put(key, String.valueOf(value));
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#getLong(java.lang.String, long)
+     */
+    public long getLong(String key, long def) {
+        long result = def;
+        final String value = this.get(key, null);
+        if ( value != null ) {
+            try {
+                result = Long.parseLong(value);
+            } catch (NumberFormatException ignore) {
+                // return the default value
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#putBoolean(java.lang.String, boolean)
+     */
+    public void putBoolean(String key, boolean value) {
+        this.put(key, String.valueOf(value));
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#getBoolean(java.lang.String, boolean)
+     */
+    public boolean getBoolean(String key, boolean def) {
+        boolean result = def;
+        final String value = this.get(key, null);
+        if ( value != null ) {
+            if ( value.equalsIgnoreCase("true") ) {
+                result = true;
+            } else if ( value.equalsIgnoreCase("false") ) {
+                result = false;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#putFloat(java.lang.String, float)
+     */
+    public void putFloat(String key, float value) {
+        this.put(key, String.valueOf(value));
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#getFloat(java.lang.String, float)
+     */
+    public float getFloat(String key, float def) {
+        float result = def;
+        final String value = this.get(key, null);
+        if ( value != null ) {
+            try {
+                result = Float.parseFloat(value);
+            } catch (NumberFormatException ignore) {
+                // return the default value
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#putDouble(java.lang.String, double)
+     */
+    public void putDouble(String key, double value) {
+        this.put(key, String.valueOf(value));
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#getDouble(java.lang.String, double)
+     */
+    public double getDouble(String key, double def) {
+        double result = def;
+        final String value = this.get(key, null);
+        if ( value != null ) {
+            try {
+                result = Double.parseDouble(value);
+            } catch (NumberFormatException ignore) {
+                // return the default value
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#putByteArray(java.lang.String, byte[])
+     */
+    public void putByteArray(String key, byte[] value) {
+        this.checkKey(key);
+        this.checkValue(value);
+        try {
+            this.put(key, new String(Base64.decodeBase64(value), "utf-8"));
+        } catch (UnsupportedEncodingException ignore) {
+            // utf-8 is always available
+        }
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#getByteArray(java.lang.String, byte[])
+     */
+    public byte[] getByteArray(String key, byte[] def) {
+        byte[] result = def;
+        String value = this.get(key, null);
+        if ( value != null ) {
+            try {
+                result = Base64.decodeBase64(value.getBytes("utf-8"));
+            } catch (UnsupportedEncodingException ignore) {
+                // utf-8 is always available
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#keys()
+     */
+    public synchronized String[] keys() throws BackingStoreException {
+        this.sync();
+        final Set keys = this.properties.keySet();
+        return (String[])keys.toArray(new String[keys.size()]);
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#childrenNames()
+     */
+    public synchronized String[] childrenNames() throws BackingStoreException {
+        this.sync();
+        final Set names = this.children.keySet();
+        return (String[])names.toArray(new String[names.size()]);
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#parent()
+     */
+    public Preferences parent() {
+        this.checkValidity();
+        return this.parent;
+    }
+
+    /**
+     * We do not synchronize this method to avoid dead locks as this
+     * method might call another preferences object in the hierarchy.
+     * @see org.osgi.service.prefs.Preferences#node(java.lang.String)
+     */
+    public Preferences node(String pathName) {
+        if ( pathName == null ) {
+            throw new NullPointerException("Path must not be null.");
+        }
+        PreferencesImpl executingNode= this;
+        synchronized ( this ) {
+            this.checkValidity();
+            if ( pathName.length() == 0 ) {
+                return this;
+            }
+            if ( pathName.startsWith("/") && this.parent != null ) {
+                executingNode = this.getRoot();
+            }
+            if ( pathName.startsWith("/") ) {
+                pathName = pathName.substring(1);
+            }
+        }
+        return executingNode.getNode(pathName, true, true);
+    }
+
+    /**
+     * Get or create the node.
+     * If the node already exists, it's just returned. If not
+     * it is created.
+     * @param pathName
+     * @return
+     */
+    public PreferencesImpl getOrCreateNode(String pathName) {
+        if ( pathName == null ) {
+            throw new NullPointerException("Path must not be null.");
+        }
+        PreferencesImpl executingNode= this;
+        if ( pathName.length() == 0 ) {
+            return this;
+        }
+        if ( pathName.startsWith("/") && this.parent != null ) {
+            executingNode = this.getRoot();
+        }
+        if ( pathName.startsWith("/") ) {
+            pathName = pathName.substring(1);
+        }
+        return executingNode.getNode(pathName, false, true);
+    }
+
+    /**
+     * Get a relative node.
+     * @param path
+     * @return
+     */
+    protected PreferencesImpl getNode(String path, boolean saveNewlyCreatedNode, boolean create) {
+        if ( path.startsWith("/") ) {
+            throw new IllegalArgumentException("Path must not contained consecutive slashes");
+        }
+        if ( path.length() == 0 ) {
+            return this;
+        }
+        synchronized ( this ) {
+            this.checkValidity();
+
+            String subPath = null;
+            int pos = path.indexOf('/');
+            if ( pos != -1 ) {
+                subPath = path.substring(pos+1);
+                path = path.substring(0, pos);
+            }
+            boolean save = false;
+            PreferencesImpl child = (PreferencesImpl) this.children.get(path);
+            if ( child == null ) {
+                if ( !create ) {
+                    return null;
+                }
+                child = new PreferencesImpl(this, path);
+                this.children.put(path, child);
+                this.changeSet.childAdded(path);
+                if ( saveNewlyCreatedNode ) {
+                    save = true;
+                }
+                saveNewlyCreatedNode = false;
+            }
+            final PreferencesImpl result;
+            if ( subPath == null ) {
+                result = child;
+            } else {
+                result = child.getNode(subPath, saveNewlyCreatedNode, create);
+            }
+            if ( save ) {
+                try {
+                    result.flush();
+                } catch (BackingStoreException ignore) {
+                    // we ignore this for now
+                }
+            }
+            return result;
+        }
+    }
+
+    /**
+     * We do not synchronize this method to avoid dead locks as this
+     * method might call another preferences object in the hierarchy.
+     * @see org.osgi.service.prefs.Preferences#nodeExists(java.lang.String)
+     */
+    public boolean nodeExists(String pathName) throws BackingStoreException {
+        if ( pathName == null ) {
+            throw new NullPointerException("Path must not be null.");
+        }
+        if ( pathName.length() == 0 ) {
+            return this.valid;
+        }
+        PreferencesImpl node = this;
+        synchronized ( this ) {
+            this.checkValidity();
+            if ( pathName.startsWith("/") && this.parent != null ) {
+                node = this.getRoot();
+            }
+            if ( pathName.startsWith("/") ) {
+                pathName = pathName.substring(1);
+            }
+        }
+        final Preferences searchNode = node.getNode(pathName, false, false);
+        return searchNode != null;
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#removeNode()
+     */
+    public void removeNode() throws BackingStoreException {
+        this.checkValidity();
+        this.safelyRemoveNode();
+
+        if ( this.parent != null ) {
+            this.parent.removeChild(this);
+        }
+    }
+
+    /**
+     * Safely remove a node by resetting all properties and calling
+     * this method on all children recursively.
+     */
+    protected void safelyRemoveNode() {
+        if ( this.valid ) {
+            Collection c = null;
+            synchronized ( this ) {
+                this.valid = false;
+                this.properties.clear();
+                c = new ArrayList(this.children.values());
+                this.children.clear();
+            }
+            final Iterator i = c.iterator();
+            while ( i.hasNext() ) {
+                final PreferencesImpl child = (PreferencesImpl) i.next();
+                child.safelyRemoveNode();
+            }
+        }
+    }
+
+    protected synchronized void removeChild(PreferencesImpl child) {
+        this.children.remove(child.name());
+        this.changeSet.childRemoved(child.name());
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#name()
+     */
+    public String name() {
+        return this.name;
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#absolutePath()
+     */
+    public String absolutePath() {
+        if (this.parent == null) {
+            return "/";
+        } else {
+            final String parentPath = this.parent.absolutePath();
+            if ( parentPath.length() == 1 ) {
+                return parentPath + this.name;
+            }
+            return parentPath + '/' + this.name;
+        }
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#flush()
+     */
+    public synchronized void flush() throws BackingStoreException {
+        this.checkValidity();
+        this.storeManager.getStore().store(this);
+        this.changeSet.clear();
+    }
+
+    /**
+     * @see org.osgi.service.prefs.Preferences#sync()
+     */
+    public synchronized void sync() throws BackingStoreException {
+        this.checkValidity();
+        this.storeManager.getStore().update(this);
+        this.storeManager.getStore().store(this);
+    }
+
+    /**
+     * Update from the preferences impl.
+     * @param impl
+     */
+    public void update(PreferencesImpl impl) {
+        final Iterator i = impl.properties.entrySet().iterator();
+        while ( i.hasNext() ) {
+            final Map.Entry entry = (Map.Entry)i.next();
+            if ( !this.properties.containsKey(entry.getKey()) ) {
+                this.properties.put(entry.getKey(), entry.getValue());
+            }
+        }
+        final Iterator cI = impl.children.entrySet().iterator();
+        while ( cI.hasNext() ) {
+            final Map.Entry entry = (Map.Entry)cI.next();
+            final String name = entry.getKey().toString();
+            final PreferencesImpl child = (PreferencesImpl)entry.getValue();
+            if ( !this.children.containsKey(name) ) {
+                // create node
+                this.node(name);
+            }
+            ((PreferencesImpl)this.children.get(name)).update(child);
+        }
+    }
+
+    /***
+     * Apply the changes done to the passed preferences object.
+     * @param prefs
+     */
+    public void applyChanges(PreferencesImpl prefs) {
+        final ChangeSet changeSet = prefs.getChangeSet();
+        if ( changeSet.hasChanges ) {
+            this.changeSet.importChanges(prefs.changeSet);
+            Iterator i;
+            // remove properties
+            i = changeSet.removedProperties.iterator();
+            while ( i.hasNext() ) {
+                this.properties.remove(i.next());
+            }
+            // set/update properties
+            i = changeSet.changedProperties.iterator();
+            while ( i.hasNext() ) {
+                final String key = (String)i.next();
+                this.properties.put(key, prefs.properties.get(key));
+            }
+            // remove children
+            i = changeSet.removedChildren.iterator();
+            while ( i.hasNext() ) {
+                final String name = (String)i.next();
+                this.children.remove(name);
+            }
+            // added childs are processed in the next loop
+        }
+        final Iterator cI = prefs.getChildren().iterator();
+        while ( cI.hasNext() ) {
+            final PreferencesImpl current = (PreferencesImpl)cI.next();
+            final PreferencesImpl child = this.getOrCreateNode(current.name());
+            child.applyChanges(current);
+        }
+    }
+}
\ No newline at end of file
diff --git a/prefs/src/main/java/org/apache/felix/sandbox/preferences/impl/DataFileBackingStoreImpl.java b/prefs/src/main/java/org/apache/felix/sandbox/preferences/impl/DataFileBackingStoreImpl.java
new file mode 100644
index 0000000..55ed28d
--- /dev/null
+++ b/prefs/src/main/java/org/apache/felix/sandbox/preferences/impl/DataFileBackingStoreImpl.java
@@ -0,0 +1,195 @@
+/*
+ * 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.sandbox.preferences.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.felix.sandbox.preferences.BackingStoreManager;
+import org.apache.felix.sandbox.preferences.PreferencesDescription;
+import org.apache.felix.sandbox.preferences.PreferencesImpl;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.prefs.BackingStoreException;
+
+/**
+ * This implementating of the backing store uses the bundle mechanism to store
+ * binary data.
+ */
+public class DataFileBackingStoreImpl extends StreamBackingStoreImpl {
+
+    /** The root directory (or null if not available) */
+    protected final File rootDirectory;
+
+    public DataFileBackingStoreImpl(BundleContext context) {
+        super(context);
+        this.rootDirectory = context.getDataFile("");
+    }
+
+    /**
+     * @see org.apache.felix.sandbox.preferences.impl.StreamBackingStoreImpl#checkAccess()
+     */
+    protected void checkAccess() throws BackingStoreException {
+        if ( this.rootDirectory == null ) {
+            throw new BackingStoreException("Saving of data files to the bundle context is currently not supported.");
+        }
+    }
+
+    /**
+     * @see org.apache.felix.sandbox.preferences.impl.StreamBackingStoreImpl#getOutputStream(org.apache.felix.sandbox.preferences.PreferencesDescription)
+     */
+    protected OutputStream getOutputStream(PreferencesDescription desc) throws IOException {
+        final File file = this.getFile(desc);
+        return new FileOutputStream(file);
+    }
+
+    /**
+     * @see org.apache.felix.sandbox.preferences.BackingStore#availableBundles()
+     */
+    public Long[] availableBundles() {
+         // If the root directory is not available, then we do nothing!
+        try {
+            this.checkAccess();
+        } catch (BackingStoreException ignore) {
+            return new Long[0];
+        }
+        final Set bundleIds = new HashSet();
+        final File[] children = this.rootDirectory.listFiles();
+        for( int i=0; i<children.length; i++ ) {
+            final File current = children[i];
+
+            final PreferencesDescription desc = this.getDescription(current);
+            if ( desc != null ) {
+                bundleIds.add(desc.getBundleId());
+            }
+        }
+        return (Long[])bundleIds.toArray(new Long[bundleIds.size()]);
+    }
+
+    protected PreferencesDescription getDescription(File file) {
+        final String fileName = file.getName();
+        // parse the file name to get: bundle id, user|system identifer
+        if ( fileName.startsWith("P") && fileName.endsWith(".ser") ) {
+            final String name = fileName.substring(1, fileName.length() - 4);
+            final String key;
+            final String identifier;
+            int pos = name.indexOf("_");
+            if ( pos != -1 ) {
+                identifier = name.substring(pos+1);
+                key = name.substring(0, pos);
+            } else {
+                key = name;
+                identifier = null;
+            }
+            final Long bundleId = Long.valueOf(key);
+            return new PreferencesDescription(bundleId, identifier);
+        }
+        return null;
+    }
+
+    /**
+     * @see org.apache.felix.sandbox.preferences.BackingStore#remove(java.lang.Long)
+     */
+    public void remove(Long bundleId) throws BackingStoreException {
+        this.checkAccess();
+        final File[] children = this.rootDirectory.listFiles();
+        for( int i=0; i<children.length; i++ ) {
+            final File current = children[i];
+
+            final PreferencesDescription desc = this.getDescription(current);
+            if ( desc != null ) {
+                if ( desc.getBundleId().equals(bundleId) ) {
+                    current.delete();
+                }
+            }
+        }
+    }
+
+    /**
+     * @see org.apache.felix.sandbox.preferences.BackingStore#loadAll(org.apache.felix.sandbox.preferences.BackingStoreManager, java.lang.Long)
+     */
+    public PreferencesImpl[] loadAll(BackingStoreManager manager, Long bundleId) throws BackingStoreException {
+        this.checkAccess();
+        final List list = new ArrayList();
+        final File[] children = this.rootDirectory.listFiles();
+        for( int i=0; i<children.length; i++ ) {
+            final File current = children[i];
+
+            final PreferencesDescription desc = this.getDescription(current);
+            if ( desc != null ) {
+                if ( desc.getBundleId().equals(bundleId) ) {
+                    final PreferencesImpl root = new PreferencesImpl(desc, manager);
+                    try {
+                        final FileInputStream fis = new FileInputStream(current);
+                        this.read(root, fis);
+                        fis.close();
+                    } catch (IOException ioe) {
+                        throw new BackingStoreException("Unable to load preferences.", ioe);
+                    }
+                    list.add(root);
+                }
+            }
+        }
+        return (PreferencesImpl[])list.toArray(new PreferencesImpl[list.size()]);
+    }
+
+    /**
+     * @see org.apache.felix.sandbox.preferences.BackingStore#load(org.apache.felix.sandbox.preferences.BackingStoreManager, org.apache.felix.sandbox.preferences.PreferencesDescription)
+     */
+    public PreferencesImpl load(BackingStoreManager manager, PreferencesDescription desc) throws BackingStoreException {
+        this.checkAccess();
+        final File file = this.getFile(desc);
+        if ( file.exists() ) {
+            try {
+                final PreferencesImpl root = new PreferencesImpl(desc, manager);
+                final FileInputStream fis = new FileInputStream(file);
+                this.read(root, fis);
+                fis.close();
+
+                return root;
+            } catch (IOException ioe) {
+                throw new BackingStoreException("Unable to load preferences.", ioe);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the file fo the preferences tree.
+     * @param desc
+     * @return
+     */
+    protected File getFile(PreferencesDescription desc) {
+        final StringBuffer buffer = new StringBuffer("P");
+        buffer.append(desc.getBundleId());
+        if ( desc.getIdentifier() != null ) {
+            buffer.append('_');
+            buffer.append(desc.getIdentifier());
+        }
+        buffer.append(".ser");
+        final File file = new File(this.rootDirectory, buffer.toString());
+        return file;
+    }
+}
diff --git a/prefs/src/main/java/org/apache/felix/sandbox/preferences/impl/PreferencesManager.java b/prefs/src/main/java/org/apache/felix/sandbox/preferences/impl/PreferencesManager.java
new file mode 100644
index 0000000..a2849af
--- /dev/null
+++ b/prefs/src/main/java/org/apache/felix/sandbox/preferences/impl/PreferencesManager.java
@@ -0,0 +1,248 @@
+/*
+ * 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.sandbox.preferences.impl;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.felix.sandbox.preferences.BackingStore;
+import org.apache.felix.sandbox.preferences.BackingStoreManager;
+import org.apache.felix.sandbox.preferences.PreferencesImpl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.log.LogService;
+import org.osgi.service.prefs.BackingStoreException;
+import org.osgi.service.prefs.PreferencesService;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * This activator registers itself as a service factory for the
+ * preferences service.
+ *
+ */
+public class PreferencesManager
+    implements BundleActivator,
+               BundleListener,
+               ServiceFactory,
+               BackingStoreManager {
+
+    /** The map of already created services. For each client bundle
+     * a new service is created.
+     */
+    protected final Map services = new HashMap();
+
+    /** The bundle context. */
+    protected BundleContext context;
+
+    /** The backing store service tracker. */
+    protected ServiceTracker storeTracker;
+
+    /** The service tracker for the log service. */
+    protected ServiceTracker logTracker;
+
+    /** The default store which is used if no service can be found. */
+    protected BackingStore defaultStore;
+
+    /** Tracking count for the store tracker to detect changes. */
+    protected int storeTrackingCount = -1;
+
+    /**
+     * @see org.osgi.framework.BundleListener#bundleChanged(org.osgi.framework.BundleEvent)
+     */
+    public void bundleChanged(BundleEvent event) {
+        if (event.getType() == BundleEvent.UNINSTALLED) {
+            final Long bundleId = new Long(event.getBundle().getBundleId());
+            synchronized ( this.services ) {
+                try {
+                    this.getStore().remove(bundleId);
+                } catch (BackingStoreException ignore) {
+                    // we ignore this for now
+                }
+                this.services.remove(bundleId);
+            }
+        }
+    }
+
+    /**
+     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
+     */
+    public void start(BundleContext context) throws Exception {
+        this.context = context;
+
+        // track the log service using a ServiceTracker
+        this.logTracker = new ServiceTracker( context, LogService.class.getName(), null );
+        this.logTracker.open();
+
+        // create the tracker for our backing store
+        this.storeTracker = new ServiceTracker( context, BackingStore.class.getName(), null);
+        this.storeTracker.open();
+
+        // register this activator as a bundle lister
+        context.addBundleListener(this);
+
+        // finally register the service factory for the preferences service
+        context.registerService(PreferencesService.class.getName(), this, null);
+    }
+
+    /**
+     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
+     */
+    public void stop(BundleContext context) throws Exception {
+        // if we get stopped, we should save all in memory representations
+        synchronized (this.services) {
+            final Iterator i = this.services.values().iterator();
+            while ( i.hasNext() ) {
+                final PreferencesServiceImpl service = (PreferencesServiceImpl)i.next();
+                this.save(service);
+            }
+            this.services.clear();
+        }
+        // stop tracking store service
+        if ( this.storeTracker != null ) {
+            this.storeTracker.close();
+            this.storeTracker = null;
+        }
+        this.defaultStore = null;
+
+        // stop tracking log service
+        if ( this.logTracker != null ) {
+            this.logTracker.close();
+            this.logTracker = null;
+        }
+
+        this.context = null;
+    }
+
+    /**
+     * @see org.osgi.framework.ServiceFactory#getService(org.osgi.framework.Bundle, org.osgi.framework.ServiceRegistration)
+     */
+    public Object getService(Bundle bundle, ServiceRegistration reg) {
+        final Long bundleId = new Long(bundle.getBundleId());
+
+        synchronized (this.services) {
+            PreferencesServiceImpl service;
+            // do we already have created a service for this bundle?
+            service = (PreferencesServiceImpl) this.services.get(bundleId);
+
+            if (service == null) {
+                // create a new service instance
+                service = new PreferencesServiceImpl(bundleId, this);
+                this.services.put(bundleId, service);
+            }
+            return service;
+        }
+    }
+
+    /**
+     * @see org.osgi.framework.ServiceFactory#ungetService(org.osgi.framework.Bundle, org.osgi.framework.ServiceRegistration, java.lang.Object)
+     */
+    public void ungetService(Bundle bundle, ServiceRegistration reg, Object s) {
+        final Long bundleId = new Long(bundle.getBundleId());
+        // we save all the preferences
+        synchronized ( this.services ) {
+            final PreferencesServiceImpl service = (PreferencesServiceImpl) this.services.get(bundleId);
+            if ( service != null ) {
+                this.save(service);
+            }
+        }
+    }
+
+    /**
+     * Save all preferences for this service.
+     * @param service
+     */
+    protected void save(PreferencesServiceImpl service) {
+        final Iterator i = service.getAllPreferences().iterator();
+        while ( i.hasNext() ) {
+            final PreferencesImpl prefs = (PreferencesImpl)i.next();
+            try {
+                this.getStore().store(prefs);
+            } catch (BackingStoreException ignore) {
+                // we ignore this
+            }
+        }
+    }
+
+    protected void log( int level, String message, Throwable t ) {
+        final LogService log = ( LogService ) this.logTracker.getService();
+        if ( log != null ) {
+            log.log( level, message, t );
+            return;
+        }
+    }
+
+    /**
+     * @see org.apache.felix.sandbox.preferences.BackingStoreManager#getStore()
+     */
+    public BackingStore getStore() {
+        // has the service changed?
+        int currentCount = this.storeTracker.getTrackingCount();
+        BackingStore service = (BackingStore) this.storeTracker.getService();
+        if ( service != null && this.storeTrackingCount < currentCount ) {
+            this.storeTrackingCount = currentCount;
+            this.cleanupStore(service);
+        }
+        if ( service == null ) {
+            // no service available use default store
+            if ( this.defaultStore == null ) {
+                synchronized ( this ) {
+                    if ( this.defaultStore == null ) {
+                        this.defaultStore = new DataFileBackingStoreImpl(this.context);
+                        this.cleanupStore(this.defaultStore);
+                    }
+                }
+            }
+            service = this.defaultStore;
+        }
+
+        return service;
+    }
+
+    /**
+     * Clean up the store and remove preferences for deleted bundles.
+     * @param store
+     */
+    protected void cleanupStore(BackingStore store) {
+        // check which bundles are available
+        final Long[] availableBundleIds = store.availableBundles();
+
+        // now check the bundles, for which we have preferences, if they are still
+        // in service and delete the preferences where the bundles are out of service.
+        // we synchronize on services in order to get not disturbed by a bundle event
+        synchronized ( this.services ) {
+            for(int i=0; i<availableBundleIds.length; i++) {
+                final Long bundleId = availableBundleIds[i];
+                final Bundle bundle = this.context.getBundle(bundleId.longValue());
+                if (bundle == null || bundle.getState() == Bundle.UNINSTALLED) {
+                    try {
+                        store.remove(bundleId);
+                    } catch (BackingStoreException ignore) {
+                        // we ignore this for now
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/prefs/src/main/java/org/apache/felix/sandbox/preferences/impl/PreferencesServiceImpl.java b/prefs/src/main/java/org/apache/felix/sandbox/preferences/impl/PreferencesServiceImpl.java
new file mode 100644
index 0000000..c17ad3a
--- /dev/null
+++ b/prefs/src/main/java/org/apache/felix/sandbox/preferences/impl/PreferencesServiceImpl.java
@@ -0,0 +1,123 @@
+/*
+ * 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.sandbox.preferences.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.sandbox.preferences.BackingStoreManager;
+import org.apache.felix.sandbox.preferences.PreferencesDescription;
+import org.apache.felix.sandbox.preferences.PreferencesImpl;
+import org.osgi.service.prefs.BackingStoreException;
+import org.osgi.service.prefs.Preferences;
+import org.osgi.service.prefs.PreferencesService;
+
+/**
+ * This is an implementation of the OSGI Preferences Service, Version 1.1.
+ */
+public class PreferencesServiceImpl implements PreferencesService {
+
+    /** This is the system preferences tree. */
+    protected PreferencesImpl systemTree;
+
+    /** This is the map containing the user preferences trees. */
+    protected final Map trees = new HashMap();
+
+    /** The service id for the bundle this service belongs to. */
+    protected final Long bundleId;
+
+    /** The backing store manager. */
+    protected final BackingStoreManager storeManager;
+
+    public PreferencesServiceImpl(Long id,
+                                  BackingStoreManager storeManager) {
+        this.bundleId = id;
+        this.storeManager = storeManager;
+        try {
+            // load prefs first
+            PreferencesImpl[] prefs = null;
+            prefs = this.storeManager.getStore().loadAll(storeManager, this.bundleId);
+            for(int i=0;i<prefs.length;i++) {
+                if ( prefs[i].getDescription().getIdentifier() == null ) {
+                    this.systemTree = prefs[i];
+                } else {
+                    this.trees.put(prefs[i].getDescription().getIdentifier(), prefs);
+                }
+            }
+        } catch (BackingStoreException e) {
+            // we ignore this here
+        }
+    }
+
+    /**
+     * @see org.osgi.service.prefs.PreferencesService#getSystemPreferences()
+     */
+    public synchronized Preferences getSystemPreferences() {
+        if ( this.systemTree == null ) {
+            this.systemTree = new PreferencesImpl(new PreferencesDescription(this.bundleId, null), this.storeManager);
+        }
+        // sync with latest version from store
+        try {
+            this.systemTree.sync();
+        } catch (BackingStoreException ignore) {
+            // we ignore this
+        }
+        return this.systemTree;
+    }
+
+    /**
+     * @see org.osgi.service.prefs.PreferencesService#getUserPreferences(java.lang.String)
+     */
+    public synchronized Preferences getUserPreferences(String name) {
+        PreferencesImpl result = (PreferencesImpl) this.trees.get(name);
+        // if the tree does not exist yet, create it
+        if (result == null) {
+            result = new PreferencesImpl(new PreferencesDescription(this.bundleId, name), this.storeManager);
+            this.trees.put(name, result);
+        }
+        // sync with latest version from store
+        try {
+            result.sync();
+        } catch (BackingStoreException ignore) {
+            // we ignore this
+        }
+        return result;
+    }
+
+    /**
+     * @see org.osgi.service.prefs.PreferencesService#getUsers()
+     */
+    public synchronized String[] getUsers() {
+        // TODO - we have to sync with the store
+        final Set userKeys = this.trees.keySet();
+        return (String[])userKeys.toArray(new String[userKeys.size()]);
+    }
+
+    protected List getAllPreferences() {
+        final List list = new ArrayList();
+        if ( this.systemTree != null ) {
+            list.add(this.systemTree);
+        }
+        list.addAll(this.trees.values());
+        return list;
+    }
+}
\ No newline at end of file
diff --git a/prefs/src/main/java/org/apache/felix/sandbox/preferences/impl/StreamBackingStoreImpl.java b/prefs/src/main/java/org/apache/felix/sandbox/preferences/impl/StreamBackingStoreImpl.java
new file mode 100644
index 0000000..8769978
--- /dev/null
+++ b/prefs/src/main/java/org/apache/felix/sandbox/preferences/impl/StreamBackingStoreImpl.java
@@ -0,0 +1,208 @@
+/*
+ * 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.sandbox.preferences.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.felix.sandbox.preferences.BackingStore;
+import org.apache.felix.sandbox.preferences.PreferencesDescription;
+import org.apache.felix.sandbox.preferences.PreferencesImpl;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.prefs.BackingStoreException;
+
+/**
+ * This is an abstract implementation of a backing store
+ * which uses streams to read/write the preferences and
+ * stores a complete preferences tree in a single stream.
+ */
+public abstract class StreamBackingStoreImpl implements BackingStore {
+
+    /** The bundle context. */
+    protected final BundleContext bundleContext;
+
+    public StreamBackingStoreImpl(BundleContext context) {
+        this.bundleContext = context;
+    }
+
+    /**
+     * This method is invoked to check if the backing store is accessible right now.
+     * @throws BackingStoreException
+     */
+    protected abstract void checkAccess() throws BackingStoreException;
+
+    /**
+     * Get the output stream to write the preferences.
+     */
+    protected abstract OutputStream getOutputStream(PreferencesDescription desc)
+    throws IOException;
+
+    /**
+     * @see org.apache.felix.sandbox.preferences.BackingStore#store(org.apache.felix.sandbox.preferences.PreferencesImpl)
+     */
+    public void store(PreferencesImpl prefs) throws BackingStoreException {
+        // do we need to store at all?
+        if ( !this.hasChanges(prefs) ) {
+            return;
+        }
+        this.checkAccess();
+        // load existing data
+        final PreferencesImpl savedData = this.load(prefs.getBackingStoreManager(), prefs.getDescription());
+        if ( savedData != null ) {
+            // merge with saved version
+            final PreferencesImpl n = savedData.getOrCreateNode(prefs.absolutePath());
+            n.applyChanges(prefs);
+            prefs = n;
+        }
+        final PreferencesImpl root = prefs.getRoot();
+        try {
+            final OutputStream os = this.getOutputStream(root.getDescription());
+            this.write(root, os);
+            os.close();
+        } catch (IOException ioe) {
+            throw new BackingStoreException("Unable to store preferences.", ioe);
+        }
+    }
+
+    /**
+     * Has the tree changes?
+     */
+    protected boolean hasChanges(PreferencesImpl prefs) {
+        if ( prefs.getChangeSet().hasChanges() ) {
+            return true;
+        }
+        final Iterator i = prefs.getChildren().iterator();
+        while ( i.hasNext() ) {
+            final PreferencesImpl current = (PreferencesImpl) i.next();
+            if ( this.hasChanges(current) ) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @see org.apache.felix.sandbox.preferences.BackingStore#update(org.apache.felix.sandbox.preferences.PreferencesImpl)
+     */
+    public void update(PreferencesImpl prefs) throws BackingStoreException {
+        final PreferencesImpl root = this.load(prefs.getBackingStoreManager(), prefs.getDescription());
+        if ( root != null ) {
+            // and now update
+            if ( root.nodeExists(prefs.absolutePath()) ) {
+                final PreferencesImpl updated = (PreferencesImpl)root.node(prefs.absolutePath());
+                prefs.update(updated);
+            }
+        }
+    }
+
+    /**
+     * Write the preferences recursively to the output stream.
+     * @param prefs
+     * @param os
+     * @throws IOException
+     */
+    protected void write(PreferencesImpl prefs, OutputStream os)
+    throws IOException {
+        this.writePreferences(prefs, os);
+        final ObjectOutputStream oos = new ObjectOutputStream(os);
+        final Collection children = prefs.getChildren();
+        oos.writeInt(children.size());
+        oos.flush();
+        final Iterator i = children.iterator();
+        while ( i.hasNext() ) {
+            final PreferencesImpl child = (PreferencesImpl) i.next();
+            final byte[] name = child.name().getBytes("utf-8");
+            oos.writeInt(name.length);
+            oos.write(name);
+            oos.flush();
+            this.write(child, os);
+        }
+    }
+
+    /**
+     * Read the preferences recursively from the input stream.
+     * @param prefs
+     * @param is
+     * @throws IOException
+     */
+    protected void read(PreferencesImpl prefs, InputStream is)
+    throws IOException {
+        this.readPreferences(prefs, is);
+        final ObjectInputStream ois = new ObjectInputStream(is);
+        final int numberOfChilren = ois.readInt();
+        for(int i=0; i<numberOfChilren; i++) {
+            int length = ois.readInt();
+            final byte[] name = new byte[length];
+            ois.readFully(name);
+            final PreferencesImpl impl = (PreferencesImpl)prefs.node(new String(name, "utf-8"));
+            this.read(impl, is);
+        }
+    }
+
+    /**
+     * Load this preferences from an input stream.
+     * Currently the prefs are read from an object input stream and
+     * the serialization is done by hand.
+     * The changeSet is neither updated nor cleared in order to provide
+     * an update/sync functionality. This has to be done at a higher level.
+     */
+    protected void readPreferences(PreferencesImpl prefs, InputStream in) throws IOException {
+        final ObjectInputStream ois = new ObjectInputStream(in);
+        final int size = ois.readInt();
+        for(int i=0; i<size; i++) {
+            int keyLength = ois.readInt();
+            int valueLength = ois.readInt();
+            final byte[] key = new byte[keyLength];
+            final byte[] value = new byte[valueLength];
+            ois.readFully(key);
+            ois.readFully(value);
+            prefs.getProperties().put(new String(key, "utf-8"), new String(value, "utf-8"));
+        }
+    }
+
+    /**
+     * Save this preferences to an output stream.
+     * Currently the prefs are written through an object output
+     * stream with handmade serialization of strings.
+     * The changeSet is neither updated nor cleared in order to provide
+     * an update/sync functionality. This has to be done at a higher level.
+     */
+    protected void writePreferences(PreferencesImpl prefs, OutputStream out) throws IOException {
+        final ObjectOutputStream oos = new ObjectOutputStream(out);
+        final int size = prefs.getProperties().size();
+        oos.writeInt(size);
+        final Iterator i = prefs.getProperties().entrySet().iterator();
+        while ( i.hasNext() ) {
+             final Map.Entry entry = (Map.Entry)i.next();
+             final byte[] key = entry.getKey().toString().getBytes("utf-8");
+             final byte[] value = entry.getValue().toString().getBytes("utf-8");
+             oos.writeInt(key.length);
+             oos.writeInt(value.length);
+             oos.write(key);
+             oos.write(value);
+        }
+        oos.flush();
+    }
+}