You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ace.apache.org by ma...@apache.org on 2012/07/05 14:10:06 UTC

svn commit: r1357570 [24/34] - in /ace/sandbox/marrs: cnf/ cnf/ext/ cnf/lib/ cnf/releaserepo/ cnf/repo/ cnf/repo/.obrcache/ cnf/repo/.obrcache/http%3A%2F%2Fbundles.bndtools.org.s3.amazonaws.com%2Fcom.jcraft.jsch/ cnf/repo/.obrcache/http%3A%2F%2Fbundles...

Added: ace/sandbox/marrs/org.apache.ace.client.rest/test-output/testng-reports.js
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/test-output/testng-reports.js?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.client.rest/test-output/testng-reports.js (added)
+++ ace/sandbox/marrs/org.apache.ace.client.rest/test-output/testng-reports.js Thu Jul  5 12:09:30 2012
@@ -0,0 +1,130 @@
+$(document).ready(function() {
+    $('a.navigator-link').click(function() {
+        // Extract the panel for this link
+        var panel = getPanelName($(this));
+
+        // Mark this link as currently selected
+        $('.navigator-link').parent().removeClass('navigator-selected');
+        $(this).parent().addClass('navigator-selected');
+
+        showPanel(panel);
+    });
+
+    installMethodHandlers('failed');
+    installMethodHandlers('skipped');
+    installMethodHandlers('passed', true); // hide passed methods by default
+
+    $('a.method').click(function() {
+        showMethod($(this));
+        return false;
+    });
+
+    // Hide all the panels and display the first one (do this last
+    // to make sure the click() will invoke the listeners)
+    $('.panel').hide();
+    $('.navigator-link').first().click();
+
+    // Collapse/expand the suites
+    $('a.collapse-all-link').click(function() {
+        var contents = $('.navigator-suite-content');
+        if (contents.css('display') == 'none') {
+            contents.show();
+        } else {
+            contents.hide();
+        }
+    });
+
+    // Keep the navigator div always visible
+    var $scrollingDiv = $(".navigator-root");
+    $(window).scroll(function() {
+        $scrollingDiv.css('height', $(window).height() - 65);
+        $scrollingDiv.stop()
+            .animate({"marginTop": ($(window).scrollTop() + 60) + "px"} );
+    });
+});
+
+// The handlers that take care of showing/hiding the methods
+function installMethodHandlers(name, hide) {
+    function getContent(t) {
+    return $('.method-list-content.' + name + "." + t.attr('panel-name'));
+    }
+
+    function getHideLink(t, name) {
+        var s = 'a.hide-methods.' + name + "." + t.attr('panel-name');
+        return $(s);
+    }
+
+    function getShowLink(t, name) {
+        return $('a.show-methods.' + name + "." + t.attr('panel-name'));
+    }
+
+    function getMethodPanelClassSel(element, name) {
+        var panelName = getPanelName(element);
+    var sel = '.' + panelName + "-class-" + name;
+        return $(sel);
+    }
+
+    $('a.hide-methods.' + name).click(function() {
+        var w = getContent($(this));
+        w.hide();
+        getHideLink($(this), name).hide();
+        getShowLink($(this), name).show();
+    getMethodPanelClassSel($(this), name).hide();
+    });
+
+    $('a.show-methods.' + name).click(function() {
+        var w = getContent($(this));
+        w.show();
+        getHideLink($(this), name).show();
+        getShowLink($(this), name).hide();
+    showPanel(getPanelName($(this)));
+    getMethodPanelClassSel($(this), name).show();
+    });
+
+    if (hide) {
+        $('a.hide-methods.' + name).click();
+    } else {
+        $('a.show-methods.' + name).click();
+    }
+}
+
+function getHashForMethod(element) {
+    return element.attr('hash-for-method');
+}
+
+function getPanelName(element) {
+    return element.attr('panel-name');
+}
+
+function showPanel(panelName) {
+    $('.panel').hide();
+    var panel = $('.panel[panel-name="' + panelName + '"]');
+    panel.show();
+}
+
+function showMethod(element) {
+    var hashTag = getHashForMethod(element);
+    var panelName = getPanelName(element);
+    showPanel(panelName);
+    var current = document.location.href;
+    var base = current.substring(0, current.indexOf('#'))
+    document.location.href = base + '#' + hashTag;
+    var newPosition = $(document).scrollTop() - 65;
+    $(document).scrollTop(newPosition);
+}
+
+function drawTable() {
+    for (var i = 0; i < suiteTableInitFunctions.length; i++) {
+        window[suiteTableInitFunctions[i]]();
+    }
+
+    for (var k in window.suiteTableData) {
+        var v = window.suiteTableData[k];
+        var div = v.tableDiv;
+        var data = v.tableData
+        var table = new google.visualization.Table(document.getElementById(div));
+        table.draw(data, {
+            showRowNumber : false
+        });
+    }
+}

Added: ace/sandbox/marrs/org.apache.ace.client.rest/test-output/testng-results.xml
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/test-output/testng-results.xml?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.client.rest/test-output/testng-results.xml (added)
+++ ace/sandbox/marrs/org.apache.ace.client.rest/test-output/testng-results.xml Thu Jul  5 12:09:30 2012
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<testng-results skipped="0" failed="0" total="2" passed="2">
+  <reporter-output>
+  </reporter-output>
+  <suite name="Default suite" duration-ms="429" started-at="2012-07-04T12:17:19Z" finished-at="2012-07-04T12:17:20Z">
+    <groups>
+      <group name="unit">
+        <method signature="RESTClientTest.testPathTransforms()[pri:0, instance:org.apache.ace.client.rest.RESTClientTest@44050988]" name="testPathTransforms" class="org.apache.ace.client.rest.RESTClientTest"/>
+        <method signature="RESTClientTest.testPropertyGetter()[pri:0, instance:org.apache.ace.client.rest.RESTClientTest@44050988]" name="testPropertyGetter" class="org.apache.ace.client.rest.RESTClientTest"/>
+      </group> <!-- unit -->
+    </groups>
+    <test name="Default test" duration-ms="429" started-at="2012-07-04T12:17:19Z" finished-at="2012-07-04T12:17:20Z">
+      <class name="org.apache.ace.client.rest.RESTClientTest">
+        <test-method status="PASS" signature="testPathTransforms()[pri:0, instance:org.apache.ace.client.rest.RESTClientTest@44050988]" name="testPathTransforms" duration-ms="414" started-at="2012-07-04T12:17:19Z" finished-at="2012-07-04T12:17:20Z">
+        </test-method> <!-- testPathTransforms -->
+        <test-method status="PASS" signature="testPropertyGetter()[pri:0, instance:org.apache.ace.client.rest.RESTClientTest@44050988]" name="testPropertyGetter" duration-ms="1" started-at="2012-07-04T12:17:20Z" finished-at="2012-07-04T12:17:20Z">
+        </test-method> <!-- testPropertyGetter -->
+      </class> <!-- org.apache.ace.client.rest.RESTClientTest -->
+    </test> <!-- Default test -->
+  </suite> <!-- Default suite -->
+</testng-results>

Added: ace/sandbox/marrs/org.apache.ace.client.rest/test-output/testng.css
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/test-output/testng.css?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.client.rest/test-output/testng.css (added)
+++ ace/sandbox/marrs/org.apache.ace.client.rest/test-output/testng.css Thu Jul  5 12:09:30 2012
@@ -0,0 +1,9 @@
+.invocation-failed,  .test-failed  { background-color: #DD0000; }
+.invocation-percent, .test-percent { background-color: #006600; }
+.invocation-passed,  .test-passed  { background-color: #00AA00; }
+.invocation-skipped, .test-skipped { background-color: #CCCC00; }
+
+.main-page {
+  font-size: x-large;
+}
+

Added: ace/sandbox/marrs/org.apache.ace.client.rest/test.sh
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/test.sh?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.client.rest/test.sh (added)
+++ ace/sandbox/marrs/org.apache.ace.client.rest/test.sh Thu Jul  5 12:09:30 2012
@@ -0,0 +1,49 @@
+#!/bin/bash
+#
+# Test script that sends out REST commands
+#
+
+# Check out a new workspace
+echo "*** Creating new workspace..."
+WORK=`curl -s -d dummy_data -w %{redirect_url} http://localhost:8080/client/work`
+echo "Workspace is ${WORK}"
+
+echo "*** Adding artifact, feature, distribution, target and all associations..."
+
+RND=$RANDOM
+BSN=org.apache.bundle${RND}
+VERSION=1.0.0
+NAME=${BSN}-${VERSION}
+ART=`curl -v -d "{attributes: { artifactName: '${NAME}' , mimetype: 'application/vnd.osgi.bundle', Bundle-Name: '${BSN}', Bundle-SymbolicName: '${BSN}', Bundle-Version: '${VERSION}', url: 'http://localhost:8080/obr/${NAME}.jar', artifactDescription: 'coolio', processorPid: '' }, tags: { generated: 'true' }}" -w %{redirect_url} ${WORK}/artifact`
+ARTID=`echo ${ART##*/}`
+echo "Artifact is ${ART} => ${ARTID}"
+
+FEAT=`curl -v -d "{ attributes: { name: 'feature-${RANDOM}', description: 'a feature' }, tags: {}}" -w %{redirect_url} ${WORK}/feature`
+FEATID=`echo ${FEAT##*/}`
+echo "Feature is ${FEAT} => ${FEATID}"
+
+DIST=`curl -v -d "{ attributes: { name: 'distribution-${RANDOM}', description: 'a distribution' }, tags: {}}" -w %{redirect_url} ${WORK}/distribution`
+DISTID=`echo ${DIST##*/}`
+echo "Distribution is ${DIST} => ${DISTID}"
+
+TARGET=`curl -v -d "{ attributes: { id: 'target-${RANDOM}', autoapprove: 'true' }, tags: {}}" -w %{redirect_url} ${WORK}/target`
+TARGETID=`echo ${TARGET##*/}`
+echo "Target is ${TARGET} => ${TARGETID}"
+
+ASSOC1=`curl -v -d "{ attributes: { left: '${ARTID}', leftCardinality: '1', right: '${FEATID}', rightCardinality: '1' }, tags: {}}" -w %{redirect_url} ${WORK}/artifact2feature`
+echo "Association is ${ASSOC1}"
+
+ASSOC2=`curl -v -d "{ attributes: { left: '${FEATID}', leftCardinality: '1', right: '${DISTID}', rightCardinality: '1' }, tags: {}}" -w %{redirect_url} ${WORK}/feature2distribution`
+echo "Association is ${ASSOC2}"
+
+ASSOC3=`curl -v -d "{ attributes: { left: '${DISTID}', leftCardinality: '1', right: '${TARGETID}', rightCardinality: '1' }, tags: {}}" -w %{redirect_url} ${WORK}/distribution2target`
+echo "Association is ${ASSOC3}"
+
+# Get a list of artifacts
+#curl ${WORK}/artifact
+
+# Commit the workspace
+echo "*** Committing workspace..."
+curl -v -d dummy_data ${WORK}
+
+echo "*** Done."

Added: ace/sandbox/marrs/org.apache.ace.client.rest/test/org/apache/ace/client/rest/RESTClientTest.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/test/org/apache/ace/client/rest/RESTClientTest.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.client.rest/test/org/apache/ace/client/rest/RESTClientTest.java (added)
+++ ace/sandbox/marrs/org.apache.ace.client.rest/test/org/apache/ace/client/rest/RESTClientTest.java Thu Jul  5 12:09:30 2012
@@ -0,0 +1,53 @@
+/*
+ * 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.ace.client.rest;
+
+import static org.apache.ace.test.utils.TestUtils.UNIT;
+
+import java.util.Properties;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class RESTClientTest {
+    @Test(groups = { UNIT })
+    public void testPathTransforms() {
+        String path = "one/two/last%20path";
+        RESTClientServlet s = new RESTClientServlet();
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        Mockito.when(request.getPathInfo()).thenReturn(path);
+        String[] elements = s.getPathElements(request);
+        Assert.assertEquals(elements[0], "one");
+        Assert.assertEquals(elements[1], "two");
+        Assert.assertEquals(elements[2], "last path");
+        String result = s.buildPathFromElements(elements);
+        Assert.assertEquals(result, path);
+    }
+    
+    @Test(groups = { UNIT })
+    public void testPropertyGetter() {
+        RESTClientServlet s = new RESTClientServlet();
+        Assert.assertEquals(s.getProperty(new Properties() {{ put("key", "value"); }},  "key", "notused"), "value");
+        Assert.assertEquals(s.getProperty(new Properties() {{ put("unusedkey", "value"); }},  "key", "default"), "default");
+        Assert.assertEquals(s.getProperty(null,  "key", "default"), "default");
+    }
+}

Added: ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/.classpath
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/.classpath?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/.classpath (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/.classpath Thu Jul  5 12:09:30 2012
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="aQute.bnd.classpath.container"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>

Added: ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/.project
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/.project?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/.project (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/.project Thu Jul  5 12:09:30 2012
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.apache.ace.configurator.serveruseradmin</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>bndtools.core.bndbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>bndtools.core.bndnature</nature>
+	</natures>
+</projectDescription>

Added: ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/bnd.bnd
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/bnd.bnd?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/bnd.bnd (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/bnd.bnd Thu Jul  5 12:09:30 2012
@@ -0,0 +1,5 @@
+-buildpath: osgi.core,\
+	osgi.cmpn,\
+	org.apache.felix.dependencymanager
+Private-Package: org.apache.ace.configurator.serveruseradmin
+Bundle-Activator: org.apache.ace.configurator.serveruseradmin.Activator
\ No newline at end of file

Added: ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/build.xml
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/build.xml?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/build.xml (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/build.xml Thu Jul  5 12:09:30 2012
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="project" default="build"> 
+	<import file="../cnf/build.xml"/>
+</project>

Added: ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/pom.xml
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/pom.xml?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/pom.xml (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/pom.xml Thu Jul  5 12:09:30 2012
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        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.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.ace</groupId>
+        <artifactId>ace-pom</artifactId>
+        <version>0.8.1-SNAPSHOT</version>
+        <relativePath>../pom/pom.xml</relativePath>
+    </parent>
+
+    <version>0.8.1-SNAPSHOT</version>
+    <artifactId>org.apache.ace.configurator.serveruseradmin</artifactId>
+    <packaging>bundle</packaging>
+
+    <name>Apache ACE :: Configurator :: Server UserAdmin</name>
+    <description />
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/ace/trunk/ace-configurator-serveruseradmin</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/ace/trunk/ace-configurator-serveruseradmin</developerConnection>
+        <url>http://svn.apache.org/repos/asf/ace/trunk/ace-configurator-serveruseradmin</url>
+    </scm>
+
+    <properties>
+        <import.package>
+            *
+        </import.package>
+        <private.package>
+            org.apache.ace.configurator.serveruseradmin
+        </private.package>
+        <bundle.activator>
+            org.apache.ace.configurator.serveruseradmin.Activator
+        </bundle.activator>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.dependencymanager</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

Added: ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/src/org/apache/ace/configurator/serveruseradmin/Activator.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/src/org/apache/ace/configurator/serveruseradmin/Activator.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/src/org/apache/ace/configurator/serveruseradmin/Activator.java (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator.serveruseradmin/src/org/apache/ace/configurator/serveruseradmin/Activator.java Thu Jul  5 12:09:30 2012
@@ -0,0 +1,92 @@
+/*
+ * 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.ace.configurator.serveruseradmin;
+
+import java.util.Dictionary;
+
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * This bundle configures a single server user, which is to be used until we
+ * have a full-fledged user administration system.
+ */
+public class Activator extends DependencyActivatorBase {
+
+    private final static String TEST_USER = "serverUser";
+    private final static String TEST_PASSWORD = "serverPassword";
+
+    private volatile UserAdmin m_userAdmin; /* Injected by dependency manager */
+    private volatile LogService m_log;      /* Injected by dependency manager */
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        manager.add(createComponent()
+            .setImplementation(this)
+            .add(createServiceDependency().setService(UserAdmin.class).setRequired(true))
+            .add(createServiceDependency().setService(LogService.class).setRequired(false)));
+    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // do nothing
+    }
+
+    public synchronized void start() {
+        // create users
+        createUser(TEST_USER, TEST_PASSWORD);
+    }
+
+    @SuppressWarnings("unchecked")
+    private User createUser(String username, String password) {
+        User user = (User) m_userAdmin.createRole(username, Role.USER);
+        if (user != null) {
+            Dictionary properties = user.getProperties();
+            if (properties != null) {
+                properties.put("username", username);
+            }
+            else {
+                m_log.log(LogService.LOG_ERROR, "Could not get properties for " + username);
+            }
+
+            Dictionary credentials = user.getCredentials();
+            if (credentials != null) {
+                credentials.put("password", password);
+            }
+            else {
+                m_log.log(LogService.LOG_ERROR, "Could not get credentials for " + username);
+            }
+        }
+        else {
+            try {
+                user = (User) m_userAdmin.getRole(username);
+                m_log.log(LogService.LOG_WARNING, "User " + username + " already existed.");
+            }
+            catch (ClassCastException e) {
+                m_log.log(LogService.LOG_WARNING, "Role " + username + " already existed (it's no user).");
+            }
+        }
+        return user;
+    }
+}
\ No newline at end of file

Added: ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/.classpath
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/.classpath?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/.classpath (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/.classpath Thu Jul  5 12:09:30 2012
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="aQute.bnd.classpath.container"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>

Added: ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/.project
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/.project?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/.project (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/.project Thu Jul  5 12:09:30 2012
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.apache.ace.configurator.useradmin.task</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>bndtools.core.bndbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>bndtools.core.bndnature</nature>
+	</natures>
+</projectDescription>

Added: ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/bnd.bnd
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/bnd.bnd?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/bnd.bnd (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/bnd.bnd Thu Jul  5 12:09:30 2012
@@ -0,0 +1,11 @@
+-buildpath: osgi.core,\
+	osgi.cmpn,\
+	org.apache.felix.dependencymanager,\
+	org.apache.ace.range.api;version=latest,\
+	org.apache.ace.repository.api;version=latest,\
+	org.apache.ace.repository.ext;version=latest,\
+	org.apache.ace.resourceprocessor.useradmin;version=latest
+Private-Package: org.apache.ace.configurator.useradmin.task,\
+	org.apache.ace.repository.ext,\
+	org.apache.ace.repository.ext.impl
+Bundle-Activator: org.apache.ace.configurator.useradmin.task.Activator
\ No newline at end of file

Added: ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/build.xml
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/build.xml?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/build.xml (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/build.xml Thu Jul  5 12:09:30 2012
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="project" default="build"> 
+	<import file="../cnf/build.xml"/>
+</project>

Added: ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/pom.xml
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/pom.xml?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/pom.xml (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/pom.xml Thu Jul  5 12:09:30 2012
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        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.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.ace</groupId>
+        <artifactId>ace-pom</artifactId>
+        <version>0.8.1-SNAPSHOT</version>
+        <relativePath>../pom/pom.xml</relativePath>
+    </parent>
+
+    <version>0.8.1-SNAPSHOT</version>
+    <artifactId>org.apache.ace.configurator.useradmin.task</artifactId>
+    <packaging>bundle</packaging>
+
+    <name>Apache ACE :: Configurator :: UserAdmin :: Task</name>
+    <description />
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/ace/trunk/ace-configurator-useradmin-task</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/ace/trunk/ace-configurator-useradmin-task</developerConnection>
+        <url>http://svn.apache.org/repos/asf/ace/trunk/ace-configurator-useradmin-task</url>
+    </scm>
+
+    <properties>
+        <import.package>
+            org.apache.ace.range;version=${project.version},
+            org.apache.ace.repository;version=${project.version},
+            org.apache.ace.resourceprocessor.useradmin;version=${project.version},
+            *
+        </import.package>
+        <private.package>
+            org.apache.ace.configurator.useradmin.task,
+            org.apache.ace.repository.ext,
+            org.apache.ace.repository.ext.impl
+        </private.package>
+        <bundle.activator>
+            org.apache.ace.configurator.useradmin.task.Activator
+        </bundle.activator>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.ace</groupId>
+            <artifactId>org.apache.ace.range.api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.ace</groupId>
+            <artifactId>org.apache.ace.repository.api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.ace</groupId>
+            <artifactId>org.apache.ace.repository.ext</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.ace</groupId>
+            <artifactId>org.apache.ace.resourceprocessor.useradmin</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.dependencymanager</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

Added: ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/src/org/apache/ace/configurator/useradmin/task/Activator.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/src/org/apache/ace/configurator/useradmin/task/Activator.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/src/org/apache/ace/configurator/useradmin/task/Activator.java (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/src/org/apache/ace/configurator/useradmin/task/Activator.java Thu Jul  5 12:09:30 2012
@@ -0,0 +1,53 @@
+/*
+ * 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.ace.configurator.useradmin.task;
+
+import java.util.Properties;
+
+import org.apache.ace.resourceprocessor.useradmin.UserAdminConfigurator;
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+
+/**
+ * Activator for the UserAdmin updater task.
+ */
+public class Activator extends DependencyActivatorBase {
+
+    @Override
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        Properties props = new Properties();
+        props.put("taskName", UpdateUserAdminTask.PID);
+        props.put("description", "Synchronizes the UserAdmin with the server.");
+        manager.add(createComponent()
+            .setInterface(Runnable.class.getName(), props)
+            .setImplementation(UpdateUserAdminTask.class)
+            .add(createServiceDependency().setService(UserAdminConfigurator.class).setRequired(true))
+            .add(createServiceDependency().setService(LogService.class).setRequired(false))
+            .add(createConfigurationDependency().setPid(UpdateUserAdminTask.PID))
+            );
+    }
+
+    @Override
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // Nothing to do, the runnable will be pulled automatically.
+    }
+
+}
\ No newline at end of file

Added: ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/src/org/apache/ace/configurator/useradmin/task/UpdateUserAdminTask.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/src/org/apache/ace/configurator/useradmin/task/UpdateUserAdminTask.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/src/org/apache/ace/configurator/useradmin/task/UpdateUserAdminTask.java (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator.useradmin.task/src/org/apache/ace/configurator/useradmin/task/UpdateUserAdminTask.java Thu Jul  5 12:09:30 2012
@@ -0,0 +1,221 @@
+/*
+ * 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.ace.configurator.useradmin.task;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Properties;
+
+import org.apache.ace.repository.Repository;
+import org.apache.ace.repository.ext.BackupRepository;
+import org.apache.ace.repository.ext.CachedRepository;
+import org.apache.ace.repository.ext.impl.CachedRepositoryImpl;
+import org.apache.ace.repository.ext.impl.FilebasedBackupRepository;
+import org.apache.ace.resourceprocessor.useradmin.UserAdminConfigurator;
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.log.LogService;
+
+/**
+ * UpdateUserAdminTask processes the contents of a repository containing
+ * an XML description of the users that should be present in this system's
+ * user admin.<br>
+ * <br>
+ * From the first run on, this task will keep a local copy of the user repository,
+ * so login can happen when the server is offline.
+ */
+public class UpdateUserAdminTask implements Runnable, ManagedService {
+    /**
+     * The UpdateUserAdminTask is also used as its taskName for the scheduler.
+     */
+    public static final String PID = UpdateUserAdminTask.class.getName();
+
+    public static final String KEY_REPOSITORY_LOCATION = "repositoryLocation";
+    public static final String KEY_REPOSITORY_CUSTOMER = "repositoryCustomer";
+    public static final String KEY_REPOSITORY_NAME = "repositoryName";
+
+    private static final String FILE_ROOT = "userrepositories";
+    private static final String VERSION = "version";
+
+    // Will by injected by Dependency Manager...
+    private volatile UserAdminConfigurator m_configurator;
+    private volatile LogService m_log;
+    private volatile BundleContext m_context;
+
+    private CachedRepository m_repo;
+    private BackupRepository m_backup;
+    private String m_repoFilter;
+    private File m_properties;
+    
+    /**
+     * Called by Dependency Manager upon initialization of this component.
+     * <p>
+     * Due to the dependency on the configuration; the {@link #updated(Dictionary)} method is already called!
+     * </p>
+     * 
+     * @param comp this component, cannot be <code>null</code>.
+     */
+    public void init(Component comp) {
+        final DependencyManager dm = comp.getDependencyManager();
+        // Add the required dependency to the remote repository...
+        comp.add(dm.createServiceDependency()
+            .setService(Repository.class, m_repoFilter)
+            .setCallbacks("addRepo", "removeRepo")
+            .setInstanceBound(true)
+            .setRequired(true)
+            );
+    }
+
+    /**
+     * Checks whether there are updates to the remote repository, and if so, updates the users' backend with its contents.
+     * 
+     * @see java.lang.Runnable#run()
+     */
+    public void run() {
+        try {
+            if (!m_repo.isCurrent()) {
+                m_configurator.setUsers(m_repo.checkout(true));
+                m_log.log(LogService.LOG_DEBUG, "UpdateUserAdminTask updates to a new version: " + m_repo.getMostRecentVersion());
+                saveVersion(m_properties, m_repo.getMostRecentVersion());
+            }
+        }
+        catch (IOException e) {
+            // If anything went wrong, this means the remote repository is not available;
+            // this also means the UserAdmin is left undisturbed.
+        	m_log.log(LogService.LOG_WARNING, "Error running update UserAdmin task.", e);
+        }
+    }
+
+    /**
+     * Called by Dependency Manager upon starting of this component.
+     * 
+     * @param comp this component, cannot be <code>null</code>.
+     */
+    public void start(Component comp) {
+        try {
+            // Try to read the server data
+            m_configurator.setUsers(m_repo.checkout(true));
+        }
+        catch (IOException e) {
+            try {
+                m_log.log(LogService.LOG_DEBUG, "UpdateUserAdminTask failed to load remote data; falling back to local data.");
+                // If reading remote fails, try to set whatever we have locally
+                m_configurator.setUsers(m_repo.getLocal(true));
+            }
+            catch (IOException e2) {
+                // No problem, now we just have an empty user admin...
+                m_log.log(LogService.LOG_DEBUG, "UpdateUserAdminTask failed to load local data.");
+            }
+        }
+    }
+
+    public void updated(Dictionary dict) throws ConfigurationException {
+        if (dict != null) {
+            String customer = (String) dict.get(KEY_REPOSITORY_CUSTOMER);
+            if (customer == null) {
+                throw new ConfigurationException(KEY_REPOSITORY_CUSTOMER, "Property missing.");
+            }
+            String name = (String) dict.get(KEY_REPOSITORY_NAME);
+            if (name == null) {
+                throw new ConfigurationException(KEY_REPOSITORY_NAME, "Property missing.");
+            }
+
+            String fileRoot = FILE_ROOT + File.separator + customer + File.separator + name + File.separator;
+
+            File local = getFile(fileRoot + "local");
+            File backup = getFile(fileRoot + "backup");
+            m_backup = new FilebasedBackupRepository(local, backup);
+            
+            m_properties = getFile(fileRoot + "properties");
+            
+            m_repoFilter = "(&(customer=" + customer + ")(name=" + name + "))";
+        }
+    }
+
+    /**
+     * Creates the cached repository when given a remote repository.
+     * 
+     * @param remoteRepo the remote repository to add, cannot be <code>null</code>.
+     */
+    final void addRepo(Repository remoteRepo) {
+        m_repo = new CachedRepositoryImpl(remoteRepo, m_backup, loadVersion(m_properties));
+    }
+
+    /**
+     * Removes the cached repository when given a remote repository.
+     * 
+     * @param remoteRepo the remote repository to remove, cannot be <code>null</code>.
+     */
+    final void removeRepo(Repository remoteRepo) {
+        m_repo = null;
+    }
+
+    private File getFile(String name) {
+        File result = m_context.getDataFile(name);
+        if (!result.exists()) {
+            result.getParentFile().mkdirs();
+            try {
+                if (!result.createNewFile()) {
+                    m_log.log(LogService.LOG_ERROR, "Error creating new file " + name);
+                }
+            }
+            catch (IOException e) {
+                m_log.log(LogService.LOG_ERROR, "Error creating new file " + name, e);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Loads the most recent version from the given properties file.
+     */
+    private long loadVersion(File propertiesfile) {
+        long result = CachedRepositoryImpl.UNCOMMITTED_VERSION;
+        try {
+            Properties props = new Properties();
+            props.loadFromXML(new FileInputStream(propertiesfile));
+            result = Long.parseLong((String) props.get(VERSION));
+        }
+        catch (IOException ioe) {
+            // We have no data; no problem.
+        }
+        return result;
+    }
+
+    /**
+     * Saves the most recent version to the given properties file.
+     */
+    private void saveVersion(File properties, Long version) {
+        Properties props = new Properties();
+        props.put(VERSION, version.toString());
+        try {
+            FileOutputStream fileOutputStream = new FileOutputStream(properties);
+            props.storeToXML(fileOutputStream, null);
+        }
+        catch (IOException e) {
+            m_log.log(LogService.LOG_ERROR, "UpdateUserAdminTask failed to save local version number.");
+        }
+    }
+}
\ No newline at end of file

Added: ace/sandbox/marrs/org.apache.ace.configurator/.classpath
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator/.classpath?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator/.classpath (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator/.classpath Thu Jul  5 12:09:30 2012
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" output="bin_test" path="test"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.testng.TESTNG_CONTAINER"/>
+	<classpathentry kind="con" path="aQute.bnd.classpath.container"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>

Added: ace/sandbox/marrs/org.apache.ace.configurator/.project
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator/.project?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator/.project (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator/.project Thu Jul  5 12:09:30 2012
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.apache.ace.configurator</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>bndtools.core.bndbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>bndtools.core.bndnature</nature>
+	</natures>
+</projectDescription>

Added: ace/sandbox/marrs/org.apache.ace.configurator/bnd.bnd
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator/bnd.bnd?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator/bnd.bnd (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator/bnd.bnd Thu Jul  5 12:09:30 2012
@@ -0,0 +1,7 @@
+-buildpath: osgi.core,\
+	osgi.cmpn,\
+	org.apache.felix.dependencymanager,\
+	org.apache.ace.util;version=latest,\
+	../cnf/lib/commons-io-2.0.1.jar;version=file
+Bundle-Activator: org.apache.ace.configurator.Activator
+Private-Package: org.apache.ace.configurator
\ No newline at end of file

Added: ace/sandbox/marrs/org.apache.ace.configurator/build.xml
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator/build.xml?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator/build.xml (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator/build.xml Thu Jul  5 12:09:30 2012
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="project" default="build"> 
+	<import file="../cnf/build.xml"/>
+</project>

Added: ace/sandbox/marrs/org.apache.ace.configurator/pom.xml
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator/pom.xml?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator/pom.xml (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator/pom.xml Thu Jul  5 12:09:30 2012
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        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.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.ace</groupId>
+        <artifactId>ace-pom</artifactId>
+        <version>0.8.1-SNAPSHOT</version>
+        <relativePath>../pom/pom.xml</relativePath>
+    </parent>
+
+    <version>0.8.1-SNAPSHOT</version>
+    <artifactId>org.apache.ace.configurator</artifactId>
+    <packaging>bundle</packaging>
+
+    <name>Apache ACE :: Configurator</name>
+    <description />
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/ace/trunk/ace-configurator</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/ace/trunk/ace-configurator</developerConnection>
+        <url>http://svn.apache.org/repos/asf/ace/trunk/ace-configurator</url>
+    </scm>
+    
+    <properties>
+        <import.package>
+            *
+        </import.package>
+        <private.package>
+            org.apache.ace.configurator
+        </private.package>
+        <bundle.activator>
+            org.apache.ace.configurator.Activator
+        </bundle.activator>
+    </properties>
+
+     <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.dependencymanager</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.ace</groupId>
+            <artifactId>org.apache.ace.util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>

Added: ace/sandbox/marrs/org.apache.ace.configurator/src/org/apache/ace/configurator/Activator.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator/src/org/apache/ace/configurator/Activator.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator/src/org/apache/ace/configurator/Activator.java (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator/src/org/apache/ace/configurator/Activator.java Thu Jul  5 12:09:30 2012
@@ -0,0 +1,60 @@
+/*
+ * 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.ace.configurator;
+
+import java.io.File;
+
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.log.LogService;
+
+public class Activator extends DependencyActivatorBase {
+
+    public void init(BundleContext context, DependencyManager manager) throws Exception {
+        manager.add(createComponent()
+            .setImplementation(new Configurator(new File(
+                getProperty(context.getProperty(Activator.class.getPackage().getName() + ".CONFIG_DIR"), "conf")),
+                getProperty(context.getProperty(Activator.class.getPackage().getName() + ".POLL_INTERVAL"), 2000),
+                getProperty(context.getProperty(Activator.class.getPackage().getName() + ".RECONFIG"), true)))
+            .add(createServiceDependency()
+                .setService(ConfigurationAdmin.class)
+                .setRequired(true))
+            .add(createServiceDependency()
+                .setService(LogService.class)
+                .setRequired(false)));
+    }
+
+    public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+        // do nothing
+    }
+
+    public String getProperty(String prop, String def) {
+        return (prop == null) ? def : prop;
+    }
+
+    public long getProperty(String prop, long def) {
+        return (prop == null) ? def : Long.parseLong(prop);
+    }
+
+    public boolean getProperty(String prop, boolean def) {
+        return (prop == null) ? def : Boolean.getBoolean(prop);
+    }
+}
\ No newline at end of file

Added: ace/sandbox/marrs/org.apache.ace.configurator/src/org/apache/ace/configurator/Configurator.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator/src/org/apache/ace/configurator/Configurator.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator/src/org/apache/ace/configurator/Configurator.java (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator/src/org/apache/ace/configurator/Configurator.java Thu Jul  5 12:09:30 2012
@@ -0,0 +1,411 @@
+/*
+ * 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.ace.configurator;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.log.LogService;
+
+/**
+ * Configures bundles managed by the <code>ConfigurationAdmin</code>. This Configurator uses text files as configuration
+ * files containing properties. When a configuration file is added, the properties are being read and added. If the config file is
+ * removed, the properties are removed as well.
+ * <p>
+ * The configuration files should be stored in the configuration directory (often the 'conf' directory) of the OSGi framework and
+ * should have the format: &lt;pid&gt;.cfg
+ * <p>
+ * Note: this Configurator is based upon the principle in the FileInstall bundle Peter Kriens wrote. (see
+ * http://www.aqute.biz/Code/FileInstall for more information)
+ */
+public class Configurator implements Runnable {
+
+    private static final String DELIM_START = "${";
+    private static final String DELIM_STOP = "}";
+    private static final FileFilter FILENAME_FILTER = new FileFilter() {
+        public boolean accept(File file) {
+            return !file.isHidden() && (file.getName().endsWith(".cfg") || file.isDirectory());
+        }
+    };
+    private static final String FACTORY_INSTANCE_KEY = "factory.instance.pid";
+
+    private volatile LogService m_log;                  /* injected by dependency manager */
+    private volatile ConfigurationAdmin m_configAdmin;  /* injected by dependency manager */
+    private volatile BundleContext m_context;           /* injected by dependency manager */
+
+    private final File m_configDir;
+    private final long m_pollInterval;
+    private final Map m_checksums = new HashMap();   // absolutepath -> xor(length, date)
+    private final Map m_foundFactories = new HashMap(); // absolutedirpath -> (absolutepath -> xor(length, date))
+    private Thread m_configThread;
+    private final boolean m_reconfig;
+
+    /**
+     * Instantiates a new configurator.
+     * @param dir The directory to watch.
+     * @param pollInterval The poll iterval in ms.
+     * @param reconfig Whether or not to use reconfiguration: if <code>false</code>, existing configuration
+     * values will not be overwritten, only new values (for a given pid) will be added.
+     */
+    public Configurator(File dir, long pollInterval, boolean reconfig) {
+        if ((dir == null) || !dir.isDirectory() || (pollInterval < 0)) {
+            throw new IllegalArgumentException("Bad arguments; either not an existing directory or an invalid interval.");
+        }
+        m_configDir = dir;
+        m_pollInterval = pollInterval;
+        m_reconfig = reconfig;
+    }
+
+    /**
+     * Starts the Configuration timer.
+     */
+    synchronized void start() {
+        if (m_configThread == null) {
+            m_configThread = new Thread(this, "Apache ACE Configurator");
+        }
+        m_configThread.setDaemon(true);
+        m_configThread.start();
+    }
+
+    /**
+     * Stops the Configuration timer.
+     *
+     * @throws InterruptedException
+     */
+    synchronized void stop() throws InterruptedException {
+        // Join in stop to prevent race condition, careful with bundle location setting to null
+        m_configThread.interrupt();
+        m_configThread.join();
+        m_configThread = null;
+        m_checksums.clear();
+    }
+
+    /**
+     * Starts the actual Timer task, and calls the configurator to make sure the configurations are performed. Checking whether
+     * a new configuration is present, will be done with an interval that can be defined via a system property.
+     */
+    public void run() {
+        try {
+            while (!Thread.interrupted()) {
+                doConfigs();
+                Thread.sleep(m_pollInterval);
+            }
+        }
+        catch (InterruptedException ex) {
+            // We are requested to stop.
+        }
+    }
+
+    /**
+     * Enables the actual configuring of OSGi ManagedServices. It makes sure all new configurations are added, changed
+     * configurations are updated, and old configurations are removed. Configurations are updated when the timestamp or
+     * the size of the new configuration has changed.
+     */
+    private void doConfigs() {
+        Set pids = new HashSet(m_checksums.keySet());
+
+        File[] files = m_configDir.listFiles(FILENAME_FILTER);
+        for (int i = 0; (files != null) && (i < files.length); i++) {
+            File file = files[i];
+            String pid = parsePid(file);
+
+            if (file.isDirectory()) {
+                doFactoryConfigs(pid, file.listFiles(FILENAME_FILTER));
+            }
+            else {
+                Long newChecksum = new Long(file.lastModified() ^ file.length());
+                Long oldChecksum = (Long) m_checksums.get(pid); // may be null, intended
+                if (!newChecksum.equals(oldChecksum)) {
+                    m_checksums.put(pid, newChecksum);
+                    processConfigFile(file, null);
+                }
+                pids.remove(pid);
+            }
+        }
+        for (Iterator e = pids.iterator(); e.hasNext();) {
+            String pid = (String) e.next();
+            deleteConfig(pid, null);
+            m_checksums.remove(pid);
+        }
+    }
+
+    private void doFactoryConfigs(String factoryPid, File[] newInstances) {
+        if (!m_foundFactories.containsKey(factoryPid)) {
+            m_foundFactories.put(factoryPid, new HashMap());
+        }
+        Map instances = (Map) m_foundFactories.get(factoryPid);
+        Set instancesPids = new HashSet(instances.keySet());
+
+        for (int j = 0; j < newInstances.length; j++) {
+            File instanceConfigFile = newInstances[j];
+            String instancePid = parsePid(instanceConfigFile);
+
+            Long newChecksum = new Long(instanceConfigFile.lastModified() ^ instanceConfigFile.length());
+            Long oldChecksum = (Long) instances.get(instancePid);
+            if (!newChecksum.equals(oldChecksum)) {
+                instances.put(instancePid, newChecksum);
+                processConfigFile(instanceConfigFile, factoryPid);
+            }
+            instancesPids.remove(instancePid);
+        }
+
+        for (Iterator e = instancesPids.iterator(); e.hasNext(); ) {
+            String instancePid = (String) e.next();
+            deleteConfig(instancePid, factoryPid);
+            instances.remove(instancePid);
+        }
+    }
+
+    /**
+     * Sets the Configuration and calls update() to do the actual configuration on the ManagedService. If and only if the configuration
+     * did not exist before or has changed. A configuration has changed if the length or the lastModified date has changed.
+     */
+    private void processConfigFile(File configFile, String factoryPid) {
+        InputStream in = null;
+        try {
+            in = new FileInputStream(configFile);
+            Properties properties = new Properties();
+            properties.load(in);
+            String pid = parsePid(configFile);
+            properties = substVars(properties);
+            configure(pid, factoryPid, properties);
+        }
+        catch (IOException ex) {
+            m_log.log(LogService.LOG_ERROR, "Unable to read configuration from file: " + configFile.getAbsolutePath(), ex);
+        }
+        finally {
+            if (in != null) {
+                try {
+                    in.close();
+                }
+                catch (Exception ex) {
+                    // Not much we can do
+                }
+            }
+        }
+    }
+
+    private void configure(String pid, String factoryPid, Properties properties) {
+        try {
+            Configuration config = getConfiguration(pid, factoryPid);
+            Dictionary oldProps = config.getProperties();
+            if (!m_reconfig) {
+                if (oldProps != null) {
+                    Enumeration keys = oldProps.keys();
+                    while (keys.hasMoreElements()) {
+                        String key = (String) keys.nextElement();
+                        if (properties.containsKey(key)) {
+                            properties.put(key, oldProps.get(key));
+                            m_log.log(LogService.LOG_DEBUG, "Using previously configured value for bundle=" + pid + " key=" + key);
+                        }
+                    }
+                }
+            }
+            if (factoryPid != null) {
+                properties.put(FACTORY_INSTANCE_KEY, factoryPid + "_" + pid);
+            }
+            config.update(properties);
+            m_log.log(LogService.LOG_DEBUG, "Updated configuration for pid '" + pid + "' (" + properties +")");
+        }
+        catch (IOException ex) {
+            m_log.log(LogService.LOG_ERROR, "Unable to update configuration for pid '" + pid + "'", ex);
+        }
+    }
+
+    private Configuration getConfiguration(String pid, String factoryPid) throws IOException {
+        if (factoryPid != null) {
+            Configuration[] configs = null;
+            try {
+                configs = m_configAdmin.listConfigurations("(" + FACTORY_INSTANCE_KEY + "=" + factoryPid + "_" + pid + ")");
+            }
+            catch (InvalidSyntaxException e) {
+                m_log.log(LogService.LOG_ERROR, "Exception during lookup of configuration of managed service factory instance '" + pid + "'", e);
+            }
+            if ((configs == null) || (configs.length == 0)) {
+                return m_configAdmin.createFactoryConfiguration(factoryPid, null);
+            }
+            else {
+                return configs[0];
+            }
+        }
+        else {
+            return m_configAdmin.getConfiguration(pid, null);
+        }
+    }
+
+    /**
+     * Removes a configuration from ConfigAdmin.
+     */
+    protected void deleteConfig(String pid, String factoryPid) {
+        try {
+            Configuration config = getConfiguration(pid, factoryPid);
+            config.delete();
+            m_log.log(LogService.LOG_DEBUG, "Removed configuration for pid '" + pid + "'");
+        }
+        catch (Exception e) {
+            m_log.log(LogService.LOG_ERROR, "Unable to remove configuration for pid '" + pid + "'", e);
+        }
+    }
+
+    /**
+     * Remove the config extension (.cfg) and return the resulting String.
+     */
+    protected String parsePid(File file) {
+        String name = file.getName();
+        if (file.isDirectory()) {
+            // factory pid
+            return name;
+        }
+        else {
+            return name.substring(0, name.length() - 4);
+        }
+    }
+
+
+    /**
+     * Performs variable substitution for a complete set of properties
+     *
+     * @see #substVars(String, String, java.util.Map, java.util.Properties)
+     * @param properties Set of properties to apply substitution on.
+     * @return Same set of properties with all variables substituted.
+     */
+    private Properties substVars(Properties properties) {
+        for (Enumeration propertyKeys = properties.propertyNames(); propertyKeys.hasMoreElements(); ) {
+            String name = (String) propertyKeys.nextElement();
+            String value = properties.getProperty(name);
+            properties.setProperty(name, substVars(value, name, null, properties));
+        }
+        return properties;
+    }
+
+    /**
+     * <p>
+     * This method performs property variable substitution on the specified value. If the specified value contains the syntax
+     * <tt>${&lt;prop-name&gt;}</tt>, where <tt>&lt;prop-name&gt;</tt> refers to either a configuration property or a
+     * system property, then the corresponding property value is substituted for the variable placeholder. Multiple variable
+     * placeholders may exist in the specified value as well as nested variable placeholders, which are substituted from inner
+     * most to outer most. Configuration properties override system properties.
+     * </p>
+     *
+     * @param val The string on which to perform property substitution.
+     * @param currentKey The key of the property being evaluated used to detect cycles.
+     * @param cycleMap Map of variable references used to detect nested cycles.
+     * @param configProps Set of configuration properties.
+     * @return The value of the specified string after system property substitution.
+     * @throws IllegalArgumentException If there was a syntax error in the property placeholder syntax or a recursive variable
+     *         reference.
+     */
+    private String substVars(String val, String currentKey, Map cycleMap, Properties configProps) throws IllegalArgumentException {
+        // If there is currently no cycle map, then create
+        // one for detecting cycles for this invocation.
+        if (cycleMap == null) {
+            cycleMap = new HashMap();
+        }
+
+        // Put the current key in the cycle map.
+        cycleMap.put(currentKey, currentKey);
+
+        // Assume we have a value that is something like:
+        // "leading ${foo.${bar}} middle ${baz} trailing"
+
+        // Find the first ending '}' variable delimiter, which
+        // will correspond to the first deepest nested variable
+        // placeholder.
+        int stopDelim = val.indexOf(DELIM_STOP);
+
+        // Find the matching starting "${" variable delimiter
+        // by looping until we find a start delimiter that is
+        // greater than the stop delimiter we have found.
+        int startDelim = val.indexOf(DELIM_START);
+        while (stopDelim >= 0) {
+            int idx = val.indexOf(DELIM_START, startDelim + DELIM_START.length());
+            if ((idx < 0) || (idx > stopDelim)) {
+                break;
+            }
+            else if (idx < stopDelim) {
+                startDelim = idx;
+            }
+        }
+
+        // If we do not have a start or stop delimiter, then just
+        // return the existing value.
+        if ((startDelim < 0) && (stopDelim < 0)) {
+            return val;
+        }
+        // At this point, we found a stop delimiter without a start,
+        // so throw an exception.
+        else if (((startDelim < 0) || (startDelim > stopDelim)) && (stopDelim >= 0)) {
+            throw new IllegalArgumentException("stop delimiter with no start delimiter: " + val);
+        }
+
+        // At this point, we have found a variable placeholder so
+        // we must perform a variable substitution on it.
+        // Using the start and stop delimiter indices, extract
+        // the first, deepest nested variable placeholder.
+        String variable = val.substring(startDelim + DELIM_START.length(), stopDelim);
+
+        // Verify that this is not a recursive variable reference.
+        if (cycleMap.get(variable) != null) {
+            throw new IllegalArgumentException("recursive variable reference: " + variable);
+        }
+
+        // Get the value of the deepest nested variable placeholder.
+        // Try to configuration properties first.
+        String substValue = (configProps != null) ? configProps.getProperty(variable, null) : null;
+        if (substValue == null) {
+            // Ignore unknown property values.
+            substValue = m_context.getProperty(variable);
+            if (substValue == null) {
+                substValue = "";
+            }
+        }
+
+        // Remove the found variable from the cycle map, since
+        // it may appear more than once in the value and we don't
+        // want such situations to appear as a recursive reference.
+        cycleMap.remove(variable);
+
+        // Append the leading characters, the substituted value of
+        // the variable, and the trailing characters to get the new
+        // value.
+        val = val.substring(0, startDelim) + substValue + val.substring(stopDelim + DELIM_STOP.length(), val.length());
+
+        // Now perform substitution again, since there could still
+        // be substitutions to make.
+        val = substVars(val, currentKey, cycleMap, configProps);
+
+        // Return the value.
+        return val;
+    }
+}
\ No newline at end of file

Added: ace/sandbox/marrs/org.apache.ace.configurator/test/org/apache/ace/configurator/ConfiguratorTest.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.configurator/test/org/apache/ace/configurator/ConfiguratorTest.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.configurator/test/org/apache/ace/configurator/ConfiguratorTest.java (added)
+++ ace/sandbox/marrs/org.apache.ace.configurator/test/org/apache/ace/configurator/ConfiguratorTest.java Thu Jul  5 12:09:30 2012
@@ -0,0 +1,372 @@
+/*
+ * 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.ace.configurator;
+
+import static org.apache.ace.test.utils.TestUtils.UNIT;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Dictionary;
+import java.util.Properties;
+
+import org.apache.ace.test.utils.FileUtils;
+import org.apache.ace.test.utils.TestUtils;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.log.LogService;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class ConfiguratorTest {
+
+    private Configurator m_configurator;
+    private File m_configDir;
+    private ConfigurationAdmin m_configAdmin;
+
+    @BeforeMethod(alwaysRun = true)
+    protected void setUp() throws Exception {
+        setUp(false);
+    }
+
+    /**
+     * Sets up the environment for testing.
+     * @param reconfig Indicates whether or not the configurator should use reconfiguration.
+     */
+    protected void setUp(boolean reconfig) throws Exception {
+        m_configAdmin = new MockConfigAdmin();
+
+        m_configDir = FileUtils.createTempFile(null);
+        m_configDir.mkdir();
+        m_configurator = new Configurator(m_configDir, 400, reconfig);
+
+        TestUtils.configureObject(m_configurator, ConfigurationAdmin.class, m_configAdmin);
+        TestUtils.configureObject(m_configurator, LogService.class);
+        TestUtils.configureObject(m_configurator, BundleContext.class, TestUtils.createMockObjectAdapter(BundleContext.class, new Object() {
+            @SuppressWarnings("unused")
+            public String getProperty(String key) {
+                return "contextProp";
+            }
+        }));
+        m_configurator.start();
+    }
+
+    /**
+     * save the properties into a configuration file the configurator can read.
+     * The file is first created and then moved to make sure the configuration doesn't read an empty file
+     */
+    private void saveConfiguration(String servicePid, Properties configuration) {
+        saveConfiguration(servicePid, null, configuration);
+    }
+
+    /**
+     * save the properties into a configuration file stored in a directory reflecting the factory pid
+     */
+    private void saveConfiguration(String servicePid, String factoryPid, Properties configuration) {
+        OutputStream fileOutputStream = null;
+        File outFile = null;
+        try {
+            outFile = FileUtils.createTempFile(null);
+            fileOutputStream = new FileOutputStream(outFile);
+            configuration.store(fileOutputStream, null);
+        } catch (IOException ioe) {
+            // the test will fail, ignore this.
+        } finally {
+            if (fileOutputStream != null) {
+                try {
+                    fileOutputStream.close();
+                }
+                catch (IOException e) {
+                    // nothing we can do
+                }
+            }
+        }
+        if (outFile != null) {
+            if (factoryPid == null) {
+                File dest = new File(m_configDir, servicePid + ".cfg");
+                if (dest.exists()) {
+                    dest.delete();
+                }
+                renameFile(outFile, dest);
+            }
+            else {
+                File file = new File(m_configDir, factoryPid);
+                file.mkdirs();
+                File dest = new File(file, servicePid + ".cfg");
+                if (dest.exists()) {
+                    dest.delete();
+                }
+                renameFile(outFile, dest);
+            }
+        }
+    }
+
+    // remove a created configuration file
+    private void removeConfiguration(String servicePid) {
+        removeConfiguration(servicePid, null);
+    }
+
+    private void removeConfiguration(String servicePid, String factoryPid) {
+        if (factoryPid != null) {
+            new File(m_configDir, factoryPid + File.separator + servicePid + ".cfg").delete();
+        } else {
+            new File(m_configDir, servicePid + ".cfg").delete();
+        }
+    }
+
+    // set some standard properties for testing
+    private Properties createProperties() {
+        Properties props = new Properties();
+        props.put("test", "value1");
+        props.put("test2", "value2");
+        return props;
+    }
+
+    // add a configuration
+    @SuppressWarnings("unchecked")
+    @Test(groups = { UNIT })
+    public void testAddConfiguration() {
+        Properties initialConfiguration = createProperties();
+        saveConfiguration("test-add", initialConfiguration);
+
+        Dictionary configuration = getAndWaitForConfiguration(initialConfiguration);
+        assert configuration != null : "No configuration received from configurator";
+        assert configuration.equals(createProperties()) : "Configuration content is unexpected";
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test(groups = { UNIT })
+    public void testAddFactoryConfiguration() {
+        Properties props = createProperties();
+        saveConfiguration("test-add", "testFactory", props);
+
+        Dictionary configuration = getAndWaitForConfiguration(props);
+        assert configuration != null : "No configuration received from configurator";
+        assert "testFactory_test-add".equals(configuration.remove("factory.instance.pid")) : "Incorrect factory instance pid was added to the configuration";
+        assert configuration.equals(createProperties()) : "Configuration content is unexpected";
+    }
+
+    // remove a configuration
+    @Test(groups = { UNIT })
+    public void testRemoveFactoryConfiguration() {
+        Properties props = createProperties();
+        saveConfiguration("test-remove", "testFactory", props);
+        getAndWaitForConfiguration(props);
+
+        removeConfiguration("test-remove", "testFactory");
+
+        // after some processing time, we should get a message that the configuration is now removed.
+        long startTimeMillis = System.currentTimeMillis();
+        boolean isDeleted = false;
+        try {
+            while (!isDeleted && (System.currentTimeMillis() < startTimeMillis + 2000)) {
+                isDeleted = ((MockConfiguration) m_configAdmin.getConfiguration("")).isDeleted();
+                if (!isDeleted) {
+                    Thread.sleep(100);
+                }
+            }
+        } catch (InterruptedException ie) {
+            // not much we can do
+        }
+        catch (IOException e) {
+            // cannot come from our mock config admin
+        }
+        assert isDeleted : "The configuration is not removed as expected";
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test(groups = { UNIT })
+    public void testPropertySubstitution( ) {
+        Properties initialConfiguration = createProperties();
+        initialConfiguration.put("var", "value");
+        initialConfiguration.put("subst", "${var}");
+        saveConfiguration("test-subst", initialConfiguration);
+
+        Dictionary configuration = getAndWaitForConfiguration(initialConfiguration);
+        assert configuration != null : "No configuration received from configurator";
+        assert configuration.get("subst").equals(configuration.get("var")) : "Substitution failed";
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test(groups = { UNIT })
+    public void testPropertySubstitutionFromContext() {
+        Properties initialConfiguration = createProperties();
+        initialConfiguration.put("subst", "${var}");
+        saveConfiguration("test-subst", initialConfiguration);
+
+        Dictionary configuration = getAndWaitForConfiguration(initialConfiguration);
+        assert configuration != null : "No configuration received from configurator";
+        assert configuration.get("subst") != null : "Substitution failed";
+    }
+
+    // update a configuration, only adding a key (this is allowed in all cases)
+    @SuppressWarnings("unchecked")
+    @Test(groups = { UNIT })
+    public void testChangeConfigurationUsingNewKey() {
+        Properties initialConfiguration = createProperties();
+        saveConfiguration("test-change", initialConfiguration);
+
+        Dictionary configuration = getAndWaitForConfiguration(initialConfiguration);
+        assert configuration != null : "No configuration received from configurator";
+        assert configuration.equals(initialConfiguration) : "Configuration content not expected. Was expecting " + initialConfiguration.size() + " but got " + configuration.size();
+
+        initialConfiguration.put("anotherKey","anotherValue");
+        saveConfiguration("test-change", initialConfiguration);
+
+        // now the configuration should be updated
+        configuration = getAndWaitForConfiguration(initialConfiguration);
+        assert configuration != null : "No configuration received from configurator";
+        assert configuration.equals(initialConfiguration) : "Configuration content not expected. Was expecting " + initialConfiguration.size() + " but got " + configuration.size();
+    }
+
+    // update a configuration, changing an already existing key, not using reconfiguration
+    @SuppressWarnings("unchecked")
+    @Test(groups = { UNIT })
+    public void testChangeConfigurationUsingSameKeyNoReconfigure() {
+        Properties configurationValues = createProperties();
+        Properties initialConfigurationValues = new Properties();
+        initialConfigurationValues.putAll(configurationValues);
+        saveConfiguration("test-change", configurationValues);
+
+        Dictionary configuration = getAndWaitForConfiguration(configurationValues);
+        assert configuration != null : "No configuration received from configurator";
+        assert configuration.equals(configurationValues) : "Configuration content not expected. Was expecting " + configurationValues.size() + " but got " + configuration.size();
+
+        configurationValues.put("test","value42");
+        saveConfiguration("test-change", configurationValues);
+
+        // The update should have been ignored, and the old values should still be present.
+        configuration = getAndWaitForConfiguration(configurationValues);
+        assert configuration != null : "No configuration received from configurator";
+        assert configuration.equals(initialConfigurationValues) : "Configuration content not expected. Was expecting " + configurationValues.size() + " but got " + configuration.size();
+    }
+
+    // update a configuration, changing an already existing key, using reconfiguration
+    @SuppressWarnings("unchecked")
+    @Test(groups = { UNIT })
+    public void testChangeConfigurationUsingSameKeyWithReconfigure() throws Exception {
+        setUp(true); // Instruct the configurator to reconfigure
+        Properties configurationValues = createProperties();
+        saveConfiguration("test-change", configurationValues);
+
+        Dictionary configuration = getAndWaitForConfiguration(configurationValues);
+        assert configuration != null : "No configuration received from configurator";
+        assert configuration.equals(configurationValues) : "Configuration content not expected. Was expecting " + configurationValues.size() + " but got " + configuration.size();
+
+        configurationValues.put("test","value42");
+        saveConfiguration("test-change", configurationValues);
+
+        // now the configuration should be updated
+        configuration = getAndWaitForConfiguration(configurationValues);
+        assert configuration != null : "No configuration received from configurator";
+        assert configuration.equals(configurationValues) : "Configuration content not expected. Was expecting " + configurationValues.size() + " but got " + configuration.size();
+    }
+
+    // remove a configuration
+    @SuppressWarnings("unchecked")
+    @Test(groups = { UNIT })
+    public void testRemoveConfiguration() {
+        Properties initialConfiguration = createProperties();
+        saveConfiguration("test-remove", initialConfiguration);
+
+        Dictionary configuration = getAndWaitForConfiguration(initialConfiguration);
+        assert configuration != null : "No configuration received from configurator";
+        assert configuration.equals(createProperties()) : "Configuration content is unexpected";
+
+        // ok, the configuration is done.
+        // now try to remove it.
+        removeConfiguration("test-remove");
+
+        // after some processing time, we should get a message that the configuration is now removed.
+        long startTimeMillis = System.currentTimeMillis();
+        boolean isDeleted = false;
+        try {
+            while (!isDeleted && (System.currentTimeMillis() < startTimeMillis + 2000)) {
+                isDeleted = ((MockConfiguration) m_configAdmin.getConfiguration("")).isDeleted();
+                if (!isDeleted) {
+                    Thread.sleep(100);
+                }
+            }
+        } catch (InterruptedException ie) {
+            // not much we can do
+        }
+        catch (IOException e) {
+            // cannot come from our mock config admin
+        }
+        assert isDeleted : "The configuration is not removed as expected";
+    }
+
+    /**
+     * Get the configuration and if it not available yet wait for it.
+     * If there is still no configuration after the wait time,
+     * null is returned.
+     */
+    @SuppressWarnings("unchecked")
+    public Dictionary getAndWaitForConfiguration(Dictionary expectedConfiguration) {
+        long startTimeMillis = System.currentTimeMillis();
+        // make sure we iterate at least once
+        Dictionary configuration = null;
+        try {
+            boolean success = false;
+            while (!success && (System.currentTimeMillis() < startTimeMillis + 2000)) {
+                configuration = m_configAdmin.getConfiguration("").getProperties();
+                if (configuration != null) {
+                    synchronized(configuration) {
+                        if (expectedConfiguration.equals(configuration)) {
+                            success = true;
+                        }
+                    }
+                }
+                if (!success) {
+                    Thread.sleep(100);
+                }
+            }
+        } catch (InterruptedException ie) {
+            // not much we can do
+        }
+        catch (IOException e) {
+            // cannot come from our mock config admin
+        }
+        return configuration;
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws Exception {
+        m_configurator.stop();
+        FileUtils.removeDirectoryWithContent(m_configDir);
+    }
+
+    /**
+     * Renames a given source file to a new destination file, using Commons-IO.
+     * <p>This avoids the problem mentioned in ACE-155.</p>
+     * 
+     * @param source the file to rename;
+     * @param dest the file to rename to.
+     */
+    private void renameFile(File source, File dest) {
+        try {
+            org.apache.commons.io.FileUtils.moveFile(source, dest);
+        }
+        catch (IOException e) {
+            throw new RuntimeException("Failed to rename file!", e);
+        }
+    }
+}