You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by ja...@apache.org on 2012/09/28 14:59:03 UTC

svn commit: r1391437 [1/5] - in /felix/trunk/useradmin: ./ filestore/ filestore/src/ filestore/src/main/ filestore/src/main/java/ filestore/src/main/java/org/ filestore/src/main/java/org/apache/ filestore/src/main/java/org/apache/felix/ filestore/src/m...

Author: jawi
Date: Fri Sep 28 12:58:59 2012
New Revision: 1391437

URL: http://svn.apache.org/viewvc?rev=1391437&view=rev
Log:
FELIX-3600: imported new UserAdmin contribution.

Added:
    felix/trunk/useradmin/filestore/   (with props)
    felix/trunk/useradmin/filestore/pom.xml   (with props)
    felix/trunk/useradmin/filestore/src/
    felix/trunk/useradmin/filestore/src/main/
    felix/trunk/useradmin/filestore/src/main/java/
    felix/trunk/useradmin/filestore/src/main/java/org/
    felix/trunk/useradmin/filestore/src/main/java/org/apache/
    felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/
    felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/
    felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/
    felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/ResettableTimer.java   (with props)
    felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStore.java   (with props)
    felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositoryMemoryStore.java   (with props)
    felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositorySerializer.java   (with props)
    felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/StubGroupImpl.java   (with props)
    felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/osgi/
    felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/osgi/Activator.java   (with props)
    felix/trunk/useradmin/filestore/src/test/
    felix/trunk/useradmin/filestore/src/test/java/
    felix/trunk/useradmin/filestore/src/test/java/org/
    felix/trunk/useradmin/filestore/src/test/java/org/apache/
    felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/
    felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/
    felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/
    felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/ResettableTimerTest.java   (with props)
    felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStorePerformanceTest.java   (with props)
    felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStoreTest.java   (with props)
    felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/RoleRepositorySerializerTest.java   (with props)
    felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/StubGroupImplTest.java   (with props)
    felix/trunk/useradmin/mongodb/   (with props)
    felix/trunk/useradmin/mongodb/pom.xml   (with props)
    felix/trunk/useradmin/mongodb/src/
    felix/trunk/useradmin/mongodb/src/main/
    felix/trunk/useradmin/mongodb/src/main/java/
    felix/trunk/useradmin/mongodb/src/main/java/org/
    felix/trunk/useradmin/mongodb/src/main/java/org/apache/
    felix/trunk/useradmin/mongodb/src/main/java/org/apache/felix/
    felix/trunk/useradmin/mongodb/src/main/java/org/apache/felix/useradmin/
    felix/trunk/useradmin/mongodb/src/main/java/org/apache/felix/useradmin/mongodb/
    felix/trunk/useradmin/mongodb/src/main/java/org/apache/felix/useradmin/mongodb/KeyCodec.java   (with props)
    felix/trunk/useradmin/mongodb/src/main/java/org/apache/felix/useradmin/mongodb/MongoDB.java   (with props)
    felix/trunk/useradmin/mongodb/src/main/java/org/apache/felix/useradmin/mongodb/MongoDBStore.java   (with props)
    felix/trunk/useradmin/mongodb/src/main/java/org/apache/felix/useradmin/mongodb/MongoSerializerHelper.java   (with props)
    felix/trunk/useradmin/mongodb/src/main/java/org/apache/felix/useradmin/mongodb/RoleProvider.java   (with props)
    felix/trunk/useradmin/mongodb/src/main/java/org/apache/felix/useradmin/mongodb/osgi/
    felix/trunk/useradmin/mongodb/src/main/java/org/apache/felix/useradmin/mongodb/osgi/Activator.java   (with props)
    felix/trunk/useradmin/mongodb/src/main/java/org/apache/felix/useradmin/mongodb/osgi/LogServiceHelper.java   (with props)
    felix/trunk/useradmin/mongodb/src/test/
    felix/trunk/useradmin/mongodb/src/test/java/
    felix/trunk/useradmin/mongodb/src/test/java/org/
    felix/trunk/useradmin/mongodb/src/test/java/org/apache/
    felix/trunk/useradmin/mongodb/src/test/java/org/apache/felix/
    felix/trunk/useradmin/mongodb/src/test/java/org/apache/felix/useradmin/
    felix/trunk/useradmin/mongodb/src/test/java/org/apache/felix/useradmin/mongodb/
    felix/trunk/useradmin/mongodb/src/test/java/org/apache/felix/useradmin/mongodb/KeyCodecTest.java   (with props)
    felix/trunk/useradmin/pom.xml   (with props)
    felix/trunk/useradmin/useradmin/   (with props)
    felix/trunk/useradmin/useradmin/pom.xml   (with props)
    felix/trunk/useradmin/useradmin/src/
    felix/trunk/useradmin/useradmin/src/main/
    felix/trunk/useradmin/useradmin/src/main/java/
    felix/trunk/useradmin/useradmin/src/main/java/org/
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/BackendException.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/RoleFactory.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/RoleRepositoryStore.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/AuthorizationImpl.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/EventDispatcher.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/RoleChangeListener.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/RoleChecker.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/RoleRepository.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/UserAdminImpl.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/UserAdminListenerList.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/GroupImpl.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/ObservableDictionary.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/ObservableProperties.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/RoleImpl.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/UserImpl.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/osgi/
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/osgi/Activator.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/osgi/EventAdminHelper.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/osgi/RoleRepositoryStoreHelper.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/osgi/ServiceContext.java   (with props)
    felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/osgi/UserAdminListenerListHelper.java   (with props)
    felix/trunk/useradmin/useradmin/src/test/
    felix/trunk/useradmin/useradmin/src/test/java/
    felix/trunk/useradmin/useradmin/src/test/java/org/
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/useradmin/
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/useradmin/impl/
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/useradmin/impl/AuthorizationImplTest.java   (with props)
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/useradmin/impl/EventDispatcherTest.java   (with props)
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/useradmin/impl/MemoryRoleRepositoryStore.java   (with props)
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/useradmin/impl/RoleCheckerTest.java   (with props)
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/useradmin/impl/RoleRepositorySecurityTest.java   (with props)
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/useradmin/impl/RoleRepositoryTest.java   (with props)
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/useradmin/impl/UserAdminImplTest.java   (with props)
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/useradmin/impl/role/
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/useradmin/impl/role/GroupImplSecurityTest.java   (with props)
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/useradmin/impl/role/GroupImplTest.java   (with props)
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/useradmin/impl/role/ObservableDictionarySecurityTest.java   (with props)
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/useradmin/impl/role/ObservableDictionaryTest.java   (with props)
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/useradmin/impl/role/ObservablePropertiesTest.java   (with props)
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/useradmin/impl/role/RoleImplTest.java   (with props)
    felix/trunk/useradmin/useradmin/src/test/java/org/apache/felix/useradmin/impl/role/UserImplTest.java   (with props)

Propchange: felix/trunk/useradmin/filestore/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Fri Sep 28 12:58:59 2012
@@ -0,0 +1,20 @@
+.settings
+.classpath
+.project
+bin
+build
+target
+classes
+.wtpmodules
+.deployables
+*.iml
+*.ipr
+*.iws
+*.log
+lib
+bundle
+dist
+*.patch
+.externalToolBuilders
+maven-eclipse.xml
+

Added: felix/trunk/useradmin/filestore/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/filestore/pom.xml?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/filestore/pom.xml (added)
+++ felix/trunk/useradmin/filestore/pom.xml Fri Sep 28 12:58:59 2012
@@ -0,0 +1,100 @@
+<!--
+    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 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">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<artifactId>felix-parent</artifactId>
+		<groupId>org.apache.felix</groupId>
+		<version>1.2.0</version>
+        <relativePath>../../../pom/pom.xml</relativePath>
+	</parent>
+	<artifactId>org.apache.felix.useradmin.filestore</artifactId>
+	<version>1.0.1</version>
+	<packaging>bundle</packaging>
+	<description>Provides a file-based repository store for the UserAdmin OSGi compendium service.</description>
+	<dependencies>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.core</artifactId>
+			<version>4.0.0</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.compendium</artifactId>
+			<version>4.0.0</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.felix</groupId>
+			<artifactId>org.apache.felix.useradmin</artifactId>
+			<version>1.0.2</version>
+			<type>bundle</type>
+		</dependency>
+	</dependencies>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<extensions>true</extensions>
+				<configuration>
+					<instructions>
+						<Bundle-Name>Apache Felix User Admin file-based repository store</Bundle-Name>
+						<Bundle-Description>
+							A file-based repository store for User Admin Service of Apache Felix.
+						</Bundle-Description>
+						<Bundle-Activator>
+							${project.artifactId}.osgi.Activator
+						</Bundle-Activator>
+						<Bundle-SymbolicName>
+							${project.artifactId}
+						</Bundle-SymbolicName>
+						<Bundle-Vendor>The Apache Software Foundation</Bundle-Vendor>
+						<Import-Package>
+							org.osgi.service.useradmin; version="[1.1,1.2)",
+							org.apache.felix.useradmin; version="[1.0,1.1)",
+							*
+						</Import-Package>
+						<Export-Package>
+						</Export-Package>
+						<Private-Package>
+							${project.artifactId}.*
+						</Private-Package>
+					</instructions>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.codehaus.mojo</groupId>
+				<artifactId>rat-maven-plugin</artifactId>
+				<configuration>
+					<excludeSubProjects>false</excludeSubProjects>
+					<useEclipseDefaultExcludes>true</useEclipseDefaultExcludes>
+					<useMavenDefaultExcludes>true</useMavenDefaultExcludes>
+					<excludes>
+						<param>doc/*</param>
+						<param>maven-eclipse.xml</param>
+						<param>.checkstyle</param>
+						<param>.externalToolBuilders/*</param>
+					</excludes>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+</project>

Propchange: felix/trunk/useradmin/filestore/pom.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Added: felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/ResettableTimer.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/ResettableTimer.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/ResettableTimer.java (added)
+++ felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/ResettableTimer.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,114 @@
+/**
+ *  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.useradmin.filestore;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Provides a timer that can be reset.
+ */
+final class ResettableTimer {
+    private final ScheduledExecutorService m_executor;
+    private final Runnable m_task;
+    private final long m_timeout;
+    private final TimeUnit m_timeUnit;
+    private final AtomicReference m_futureRef;
+    
+    /**
+     * Creates a new {@link ResettableTimer} calling a given task when a given 
+     * timeout exceeds.
+     * 
+     * @param task the task to execute upon timout, cannot be <code>null</code>;
+     * @param timeout the timeout value, > 0;
+     * @param unit the time unit of the timeout value, cannot be <code>null</code>.
+     */
+    public ResettableTimer(Runnable task, long timeout, TimeUnit unit) {
+        this(new ScheduledThreadPoolExecutor(1), task, timeout, unit);
+    }
+    
+    /**
+     * Creates a new {@link ResettableTimer} calling a given task when a given 
+     * timeout exceeds.
+     * 
+     * @param executor the executor to use to execute the task, cannot be <code>null</code>;
+     * @param task the task to execute upon timout, cannot be <code>null</code>;
+     * @param timeout the timeout value, > 0;
+     * @param unit the time unit of the timeout value, cannot be <code>null</code>.
+     */
+    public ResettableTimer(ScheduledExecutorService executor, Runnable task, long timeout, TimeUnit unit) {
+        if (executor == null) { 
+            throw new IllegalArgumentException("Executor cannot be null!");
+        }
+        if (task == null) {
+            throw new IllegalArgumentException("Task cannot be null!");
+        }
+        if (timeout <= 0) { 
+            throw new IllegalArgumentException("Timeout cannot be negative!");
+        }
+        if (unit == null) { 
+            throw new IllegalArgumentException("TimeUnit cannot be null!"); 
+        }
+        
+        m_executor = executor;
+        m_task = task;
+        m_timeout = timeout;
+        m_timeUnit = unit;
+        
+        m_futureRef = new AtomicReference();
+    }
+
+    /**
+     * Schedules the task for execution with the contained timeout. If a task 
+     * is already pending or running, it will be cancelled (not interrupted). 
+     * The new task will be scheduled to run in now + timeout.
+     */
+    public ScheduledFuture schedule() {
+        ScheduledFuture currentTask = cancelCurrentTask();
+        ScheduledFuture newTask = m_executor.schedule(m_task, m_timeout, m_timeUnit);
+        m_futureRef.compareAndSet(currentTask, newTask);
+        return newTask;
+    }
+    
+    /**
+     * Shuts down this timer, allowing any pending tasks to execute. After this 
+     * method is called, {@link #schedule()} may no longer be called.
+     */
+    public void shutDown() {
+        m_executor.shutdown();
+        try {
+            m_executor.awaitTermination(2 * m_timeout, m_timeUnit);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    /**
+     * @return the current task, or <code>null</code> if no task is available.
+     */
+    private ScheduledFuture cancelCurrentTask() {
+        ScheduledFuture currentTask = (ScheduledFuture) m_futureRef.get();
+        if (currentTask != null) {
+            // Doesn't matter for completed tasks...
+            currentTask.cancel(false /* mayInterruptIfRunning */);
+        }
+        return currentTask;
+    }
+}

Propchange: felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/ResettableTimer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStore.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStore.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStore.java (added)
+++ felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStore.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,258 @@
+/**
+ *  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.useradmin.filestore;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.felix.useradmin.RoleRepositoryStore;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.useradmin.UserAdminEvent;
+import org.osgi.service.useradmin.UserAdminListener;
+
+
+/**
+ * Provides an implementation of {@link RoleRepositoryStore} using Java Serialization.
+ */
+public class RoleRepositoryFileStore extends RoleRepositoryMemoryStore implements Runnable, UserAdminListener, ManagedService {
+
+    /** The PID for this service to allow its configuration to be updated. */
+    public static final String PID = "org.apache.felix.useradmin.rolerepositoryfilestore";
+
+    static final String KEY_WRITE_DISABLED = "background.write.disabled";
+    static final String KEY_WRITE_DELAY_VALUE = "background.write.delay.value";
+    static final String KEY_WRITE_DELAY_TIMEUNIT = "background.write.delay.timeunit";
+
+    private static final boolean DEFAULT_WRITE_DISABLED = Boolean.parseBoolean(System.getProperty(KEY_WRITE_DISABLED, "false"));
+    private static final int DEFAULT_WRITE_DELAY_VALUE = Integer.parseInt(System.getProperty(KEY_WRITE_DELAY_VALUE, "500"));
+    private static final TimeUnit DEFAULT_WRITE_DELAY_TIMEUNIT = TimeUnit.MILLISECONDS;
+
+    private static final String FILE_NAME = "ua_repo.dat";
+
+    private final File m_file;
+    private final AtomicReference m_timerRef;
+
+    /**
+     * Creates a new {@link RoleRepositoryStore} instance.
+     * 
+     * @param baseDir the base directory where we can store our serialized data, cannot be <code>null</code>.
+     */
+    public RoleRepositoryFileStore(File baseDir) {
+        this(baseDir, !DEFAULT_WRITE_DISABLED);
+    }
+    
+    /**
+     * Creates a new {@link RoleRepositoryStore} instance.
+     * 
+     * @param baseDir the base directory where we can store our serialized data, cannot be <code>null</code>;
+     * @param backgroundWriteEnabled <code>true</code> if background writing should be enabled, <code>false</code> to disable it. 
+     */
+    public RoleRepositoryFileStore(File baseDir, boolean backgroundWriteEnabled) {
+        m_file = new File(baseDir, FILE_NAME);
+        
+        m_timerRef = new AtomicReference();
+
+        if (backgroundWriteEnabled) {
+            m_timerRef.set(new ResettableTimer(this, DEFAULT_WRITE_DELAY_VALUE, DEFAULT_WRITE_DELAY_TIMEUNIT));
+        }
+    }
+    
+    public void initialize() throws IOException {
+        m_entries.putAll(retrieve());
+    }
+
+    public void roleChanged(UserAdminEvent event) {
+        scheduleTask();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * <p>Will be called by m_timer!</p>
+     */
+    public void run() {
+        try {
+            // Persist everything to disk...
+            flush();
+        }
+        catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Stops this store service.
+     */
+    public void stop()  {
+        ResettableTimer timer = (ResettableTimer) m_timerRef.get();
+        if (timer != null) {
+            // Shutdown and await termination...
+            timer.shutDown();
+            // Clear reference...
+            m_timerRef.compareAndSet(timer, null);
+        }
+        
+        try {
+            // Write the latest version to disk...
+            flush();
+        }
+        catch (IOException e) {
+            // Nothing we can do about this here...
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void updated(Dictionary properties) throws ConfigurationException {
+        boolean writeDisabled = DEFAULT_WRITE_DISABLED;
+        int writeDelayValue = DEFAULT_WRITE_DELAY_VALUE;
+        TimeUnit writeDelayUnit = DEFAULT_WRITE_DELAY_TIMEUNIT;
+
+        if (properties != null) {
+            Object wd = properties.get(KEY_WRITE_DISABLED);
+            if (wd == null) {
+                throw new ConfigurationException(KEY_WRITE_DISABLED, "Missing write disabled value!");
+            }
+            try {
+                writeDisabled = Boolean.parseBoolean((String) wd);
+            } catch (Exception e) {
+                throw new ConfigurationException(KEY_WRITE_DISABLED, "Invalid write disabled value!");
+            }
+
+            if (!writeDisabled) {
+                Object wdv = properties.get(KEY_WRITE_DELAY_VALUE);
+                if (wdv == null) {
+                    throw new ConfigurationException(KEY_WRITE_DELAY_VALUE, "Missing write delay value!");
+                }
+                try {
+                    writeDelayValue = Integer.parseInt((String) wdv);
+                } catch (Exception e) {
+                    throw new ConfigurationException(KEY_WRITE_DELAY_VALUE, "Invalid write delay value!");
+                }
+                if (writeDelayValue <= 0) {
+                    throw new ConfigurationException(KEY_WRITE_DELAY_VALUE, "Invalid write delay value!");
+                }
+
+                Object wdu = properties.get(KEY_WRITE_DELAY_TIMEUNIT);
+                if (wdu != null) {
+                    try {
+                        writeDelayUnit = TimeUnit.valueOf(((String) wdu).toUpperCase());
+                    } catch (Exception e) {
+                        throw new ConfigurationException(KEY_WRITE_DELAY_TIMEUNIT, "Invalid write delay unit!");
+                    }
+                }
+            }
+        }
+
+        ResettableTimer timer = (ResettableTimer) m_timerRef.get();
+        if (timer != null) {
+            timer.shutDown();
+        }
+        m_timerRef.compareAndSet(timer, writeDisabled ? null : new ResettableTimer(this, writeDelayValue, writeDelayUnit));
+    }
+
+    /**
+     * Retrieves the serialized repository from disk.
+     * 
+     * @return the retrieved repository, never <code>null</code>.
+     * @throws IOException in case the retrieval of the repository failed.
+     */
+    protected Map retrieve() throws IOException {
+        InputStream is = null;
+
+        try {
+            is = new BufferedInputStream(new FileInputStream(m_file));
+
+            return new RoleRepositorySerializer().deserialize(is);
+        } catch (FileNotFoundException exception) {
+            // Don't bother; file does not exist...
+            return Collections.emptyMap();
+        } finally {
+            closeSafely(is);
+        }
+    }
+
+    /**
+     * Stores the given repository to disk as serialized objects.
+     * 
+     * @param roleRepository the repository to store, cannot be <code>null</code>.
+     * @throws IOException in case storing the repository failed.
+     */
+    protected void store(Map roleRepository) throws IOException {
+        OutputStream os = null;
+
+        try {
+            os = new BufferedOutputStream(new FileOutputStream(m_file));
+
+            new RoleRepositorySerializer().serialize(roleRepository, os);
+        } finally {
+            closeSafely(os);
+        }
+    }
+
+    /**
+     * Closes a given resource, ignoring any exceptions that may come out of this.
+     * 
+     * @param resource the resource to close, can be <code>null</code>.
+     */
+    private void closeSafely(Closeable resource) {
+        if (resource != null) {
+            try {
+                resource.close();
+            } catch (IOException e) {
+                // Ignore
+            }
+        }
+    }
+
+    /**
+     * Flushes the current repository to disk.
+     * 
+     * @throws IOException in case of problems storing the repository.
+     */
+    private void flush() throws IOException {
+        store(new HashMap(m_entries));
+    }
+
+    /**
+     * Notifies the background timer to schedule a task for storing the 
+     * contents of this store to disk.
+     */
+    private void scheduleTask() {
+        ResettableTimer timer = (ResettableTimer) m_timerRef.get();
+        if (timer != null) {
+            timer.schedule();
+        }
+    }
+}

Propchange: felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStore.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositoryMemoryStore.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositoryMemoryStore.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositoryMemoryStore.java (added)
+++ felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositoryMemoryStore.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,70 @@
+/**
+ *  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.useradmin.filestore;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.felix.useradmin.RoleRepositoryStore;
+import org.osgi.service.useradmin.Role;
+
+
+/**
+ * Provides a thread-safe in-memory role repository store.
+ */
+public class RoleRepositoryMemoryStore implements RoleRepositoryStore {
+    
+    protected final ConcurrentMap m_entries = new ConcurrentHashMap();
+
+    public boolean addRole(Role role) throws IOException {
+        if (role == null) {
+            throw new IllegalArgumentException("Role cannot be null!");
+        }
+        Object result = m_entries.putIfAbsent(role.getName(), role);
+        return result == null;
+    }
+
+    public void close() throws IOException {
+        // Nop
+    }
+
+    public Role[] getAllRoles() throws IOException {
+        Collection roles = m_entries.values();
+        Role[] result = new Role[roles.size()];
+        return (Role[]) roles.toArray(result);
+    }
+
+    public Role getRoleByName(String roleName) throws IOException {
+        if (roleName == null) {
+            throw new IllegalArgumentException("Role name cannot be null!");
+        }
+        return (Role) m_entries.get(roleName);
+    }
+    
+    public void initialize() throws IOException {
+        // Nop
+    }
+
+    public boolean removeRole(Role role) throws IOException {
+        if (role == null) {
+            throw new IllegalArgumentException("Role cannot be null!");
+        }
+        return m_entries.remove(role.getName(), role);
+    }
+}

Propchange: felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositoryMemoryStore.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositorySerializer.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositorySerializer.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositorySerializer.java (added)
+++ felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositorySerializer.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,454 @@
+/**
+ *  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.useradmin.filestore;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+
+import org.apache.felix.useradmin.RoleFactory;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+/**
+ * Provides a serializer for a role repository.
+ */
+final class RoleRepositorySerializer {
+    
+    private static final int VALUE_TYPE_STRING = 1;
+    private static final int VALUE_TYPE_BARRAY = 2; 
+
+    /**
+     * Deserializes a given input stream.
+     * 
+     * @param is the input stream to deserialize, cannot be <code>null</code>.
+     * @return a {@link Map} representing the role repository. It provides a 
+     *         mapping between the name of the role as key and the associated
+     *         role as value.
+     * @throws IOException in case of I/O problems;
+     * @throws IllegalArgumentException in case the given stream was <code>null</code>.
+     */
+    public Map deserialize(InputStream is) throws IOException {
+        if (is == null) {
+            throw new IllegalArgumentException("InputStream cannot be null!");
+        }
+        return readRepository(new DataInputStream(is));
+    }
+
+    /**
+     * Serializes a given map to the given output stream.
+     * 
+     * @param roleRepository the repository to serialize, cannot be <code>null</code>;
+     * @param os the output stream to serialize to, cannot be  <code>null</code>.
+     * @throws IOException in case of I/O problems;
+     * @throws IllegalArgumentException in case the given parameter was <code>null</code>.
+     */
+    public void serialize(Map roleRepository, OutputStream os) throws IOException {
+        if (roleRepository == null) {
+            throw new IllegalArgumentException("Map cannot be null!");
+        }
+        if (os == null) {
+            throw new IllegalArgumentException("OutputStream cannot be null!");
+        }
+        writeRepository(roleRepository, new DataOutputStream(os));
+    }
+    
+    /**
+     * Adds all groups, based on the given stub groups.
+     * 
+     * @param repository the repository to add the groups to, cannot be <code>null</code>;
+     * @param stubGroups the list with stub groups to replace, cannot be <code>null</code>.
+     * @throws IOException in case a referenced role was not found in the repository.
+     */
+    private void addGroups(Map repository, List stubGroups) throws IOException {
+        // First create "empty" groups in the repository; we'll fill them in later on...
+        Iterator sgIter = stubGroups.iterator();
+        while (sgIter.hasNext()) {
+            StubGroupImpl stubGroup = (StubGroupImpl) sgIter.next();
+
+            Group group = (Group) RoleFactory.createRole(Role.GROUP, stubGroup.getName());
+            copyDictionary(stubGroup.getProperties(), group.getProperties());
+            copyDictionary(stubGroup.getCredentials(), group.getCredentials());
+
+            repository.put(group.getName(), group);
+        }
+        
+        int origSize = stubGroups.size();
+        while (!stubGroups.isEmpty()) {
+            List copy = new ArrayList(stubGroups);
+            
+            int size = copy.size();
+            for (int i = 0; i < size; i++) {
+                StubGroupImpl stubGroup = (StubGroupImpl) copy.get(i);
+
+                Group group = (Group) repository.get(stubGroup.getName());
+                if (group != null) {
+                    resolveGroupMembers(stubGroup, group, repository);
+                    stubGroups.remove(stubGroup);
+                }
+            }
+
+            // In case we didn't resolve any groups; we should fail...
+            if (origSize == stubGroups.size()) {
+                throw new IOException("Failed to resolve groups: " + stubGroups);
+            }
+
+            origSize = stubGroups.size();
+        }
+    }
+    
+    /**
+     * Converts a given {@link Dictionary} implementation to a {@link Map} implementation.
+     * 
+     * @param dictionary the dictionary to convert, cannot be <code>null</code>.
+     * @return a {@link Map} instance with all the same key-value pairs as the given dictionary, never <code>null</code>.
+     */
+    private Map convertToMap(Dictionary dictionary) {
+        Map result = new HashMap();
+        if (dictionary instanceof Map) {
+            result.putAll((Map) dictionary);
+        } else {
+            Enumeration keyEnum = dictionary.keys();
+            while (keyEnum.hasMoreElements()) {
+                Object key = keyEnum.nextElement();
+                result.put(key, dictionary.get(key));
+            }
+        }
+        return result;
+    }
+    
+    /**
+     * Copies the contents of a given dictionary to a given other dictionary.
+     * 
+     * @param source the dictionary to copy from;
+     * @param dest the dictionary to copy to.
+     */
+    private void copyDictionary(Dictionary source, Dictionary dest) {
+        Enumeration keys = source.keys();
+        while (keys.hasMoreElements()) {
+            Object key = keys.nextElement();
+            Object value = source.get(key);
+            dest.put(key, value);
+        }
+    }
+
+    /**
+     * Reads and fills a given dictionary.
+     * 
+     * @param dict the dictionary to read & fill, cannot be <code>null</code>;
+     * @param dis the input stream to read the data from, cannot be <code>null</code>.
+     * @throws IOException in case of I/O problems.
+     */
+    private void readDictionary(Dictionary dict, DataInputStream dis) throws IOException {
+        // Read the number of entries...
+        int count = dis.readInt();
+        while (count-- > 0) {
+            // Read the name of the key...
+            String key = dis.readUTF();
+            // Read the type of the value...
+            int type = dis.read();
+            // Read the value & add the actual entry...
+            if (VALUE_TYPE_BARRAY == type) {
+                int length = dis.readInt();
+                byte[] value = new byte[length];
+                if (dis.read(value, 0, length) != length) {
+                    throw new IOException("Invalid repository; failed to correctly read dictionary!");
+                }
+                dict.put(key, value);
+            } else if (VALUE_TYPE_STRING == type) {
+                dict.put(key, dis.readUTF());
+            }
+        }
+    }
+    
+    /**
+     * Reads a (stub) group from the given input stream.
+     * 
+     * @param dis the input stream to read the data from, cannot be <code>null</code>.
+     * @return the read (stub) group, never <code>null</code>.
+     * @throws IOException in case of I/O problems.
+     */
+    private StubGroupImpl readGroup(DataInputStream dis) throws IOException {
+        StubGroupImpl group = new StubGroupImpl(dis.readUTF());
+        
+        readDictionary(group.getProperties(), dis);
+        readDictionary(group.getCredentials(), dis);
+        
+        // Read the number of basic members...
+        int count = dis.readInt();
+        while (count-- > 0) {
+            group.addMember(dis.readUTF());
+        }
+        
+        // Read the number of required members...
+        count = dis.readInt();
+        while (count-- > 0) {
+            group.addRequiredMember(dis.readUTF());
+        }
+        
+        return group;
+    }
+
+    /**
+     * Reads the entire repository from the given input stream.
+     * 
+     * @param dis the input stream to read the data from, cannot be <code>null</code>.
+     * @return the repository {@link Map}, never <code>null</code>.
+     * @throws IOException in case of I/O problems.
+     */
+    private Map readRepository(DataInputStream dis) throws IOException {
+        Map repository = new HashMap();
+        
+        int entryCount = dis.readInt();
+        
+        List stubGroups = new ArrayList();
+        
+        // Keep reading until no more types can be read...
+        while (entryCount-- > 0) {
+            int type = dis.readInt();
+            
+            Role role = null;
+            if (Role.GROUP == type) {
+                stubGroups.add(readGroup(dis));
+            } else if (Role.USER == type) {
+                role = readUser(dis);
+            } else {
+                role = readRole(dis);
+            }
+            
+            if (role != null) {
+                repository.put(role.getName(), role);
+            }
+        }
+        
+        // Post processing stage: replace all stub groups with real group implementations...
+        addGroups(repository, stubGroups);
+        
+        return repository;
+    }
+
+    /**
+     * Reads a role from the given input stream.
+     * 
+     * @param dis the input stream to read the data from, cannot be <code>null</code>.
+     * @return the read role, never <code>null</code>.
+     * @throws IOException in case of I/O problems.
+     */
+    private Role readRole(DataInputStream dis) throws IOException {
+        Role role = RoleFactory.createRole(Role.ROLE, dis.readUTF());
+        
+        readDictionary(role.getProperties(), dis);
+        
+        return role;
+    }
+    
+    /**
+     * Reads a user from the given input stream.
+     * 
+     * @param dis the input stream to read the data from, cannot be <code>null</code>.
+     * @return the read user, never <code>null</code>.
+     * @throws IOException in case of I/O problems.
+     */
+    private User readUser(DataInputStream dis) throws IOException {
+        User user = (User) RoleFactory.createRole(Role.USER, dis.readUTF());
+        
+        readDictionary(user.getProperties(), dis);
+        readDictionary(user.getCredentials(), dis);
+        
+        return user;
+    }
+
+    /**
+     * Resolves all basic and required group members for a given group, based on the names from the given stub group.
+     * 
+     * @param stubGroup the stub group to convert, cannot be <code>null</code>;
+     * @param repository the repository to take the roles from, cannot be <code>null</code>.
+     * @return a concrete {@link Group} instance with all members resolved, or <code>null</code> if not all members could be resolved.
+     * @throws IOException in case a referenced role was not found in the repository.
+     */
+    private void resolveGroupMembers(StubGroupImpl stubGroup, Group group, Map repository) throws IOException {
+        List names = stubGroup.getMemberNames();
+        int size = names.size();
+
+        for (int i = 0; i < size; i++) {
+            String name = (String) names.get(i);
+            Role role = (Role) repository.get(name);
+            if (role == null) {
+                throw new IOException("Unable to find referenced basic member: " + name);
+            }
+            group.addMember(role);
+        }
+        
+        names = stubGroup.getRequiredMemberNames();
+        size = names.size();
+        
+        for (int i = 0; i < size; i++) {
+            String name = (String) names.get(i);
+            Role role = (Role) repository.get(name);
+            if (role == null) {
+                throw new IOException("Unable to find referenced required member: " + name);
+            }
+            group.addRequiredMember(role);
+        }
+    }
+
+    /**
+     * Writes a given dictionary to the given output stream.
+     * 
+     * @param dict the dictionary to write, cannot be <code>null</code>;
+     * @param dos the output stream to write the data to, cannot be <code>null</code>.
+     * @throws IOException in case of I/O problems.
+     */
+    private void writeDictionary(Dictionary dict, DataOutputStream dos) throws IOException {
+        Map properties = convertToMap(dict);
+        
+        Set entries = properties.entrySet();
+        int size = entries.size();
+
+        // Write the number of entries...
+        dos.writeInt(size);
+        
+        Iterator entriesIter = entries.iterator();
+        while (entriesIter.hasNext()) {
+            Map.Entry entry = (Entry) entriesIter.next();
+            
+            dos.writeUTF((String) entry.getKey());
+            
+            Object value = entry.getValue();
+            if (value instanceof String) {
+                dos.write(VALUE_TYPE_STRING);
+                dos.writeUTF((String) value);
+            } else if (value instanceof byte[]) {
+                dos.write(VALUE_TYPE_BARRAY);
+                dos.writeInt(((byte[]) value).length);
+                dos.write((byte[]) value);
+            }
+        }
+    }
+
+    /**
+     * Writes a given group to the given output stream.
+     * 
+     * @param group the group to write, cannot be <code>null</code>.
+     * @param dos the output stream to write the data to, cannot be <code>null</code>.
+     * @throws IOException in case of I/O problems.
+     */
+    private void writeGroup(Group group, DataOutputStream dos) throws IOException {
+        dos.writeUTF(group.getName());
+
+        writeDictionary(group.getProperties(), dos);
+        writeDictionary(group.getCredentials(), dos);
+        
+        Role[] m = group.getMembers();
+
+        if (m == null) {
+            dos.writeInt(0);
+        } else {
+            // Write the number of basic members...
+            dos.writeInt(m.length);
+            // Write the names of the basic members...
+            for (int i = 0; i < m.length; i++) {
+                dos.writeUTF(m[i].getName());
+            }
+        }
+
+        m = group.getRequiredMembers();
+        
+        if (m == null) {
+            dos.writeInt(0);
+        } else {
+            // Write the number of required members...
+            dos.writeInt(m.length);
+            // Write the names of the required members...
+            for (int i = 0; i < m.length; i++) {
+                dos.writeUTF(m[i].getName());
+            }
+        }
+    }
+    
+    /**
+     * Writes the given repository to the given output stream.
+     * 
+     * @param repository the repository to write, cannot be <code>null</code>;
+     * @param dos the output stream to write the data to, cannot be <code>null</code>.
+     * @throws IOException in case of I/O problems.
+     */
+    private void writeRepository(Map repository, DataOutputStream dos) throws IOException {
+        Collection values = repository.values();
+        Iterator valuesIter = values.iterator();
+        
+        // Write the total number of entries in our repository first...
+        dos.writeInt(values.size());
+        
+        while (valuesIter.hasNext()) {
+            Role role = (Role) valuesIter.next();
+            
+            int type = role.getType();
+            
+            dos.writeInt(type);
+            
+            if (Role.GROUP == type) {
+                writeGroup((Group) role, dos);
+            } else if (Role.USER == type) {
+                writeUser((User) role, dos);
+            } else {
+                writeRole(role, dos);
+            }
+        }
+    }
+    
+    /**
+     * Writes a given role to the given output stream.
+     * 
+     * @param role the role to write, cannot be <code>null</code>.
+     * @param dos the output stream to write the data to, cannot be <code>null</code>.
+     * @throws IOException in case of I/O problems.
+     */
+    private void writeRole(Role role, DataOutputStream dos) throws IOException {
+        dos.writeUTF(role.getName());
+
+        writeDictionary(role.getProperties(), dos);
+    }
+    
+    /**
+     * Writes a given user to the given output stream.
+     * 
+     * @param user the user to write, cannot be <code>null</code>.
+     * @param dos the output stream to write the data to, cannot be <code>null</code>.
+     * @throws IOException in case of I/O problems.
+     */
+    private void writeUser(User user, DataOutputStream dos) throws IOException {
+        dos.writeUTF(user.getName());
+
+        writeDictionary(user.getProperties(), dos);
+        writeDictionary(user.getCredentials(), dos);
+    }
+}

Propchange: felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/RoleRepositorySerializer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/StubGroupImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/StubGroupImpl.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/StubGroupImpl.java (added)
+++ felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/StubGroupImpl.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,184 @@
+/**
+ *  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.useradmin.filestore;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Properties;
+
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+
+/**
+ * Provides a stub group that has "weak" references (based on their names) to its basic/required members.
+ */
+final class StubGroupImpl implements Group {
+    
+    private final String m_name;
+    private final List m_basicMembers;
+    private final List m_requiredMembers;
+    private final Dictionary m_credentials;
+    private final Dictionary m_properties;
+
+    /**
+     * Creates a new {@link StubGroupImpl} instance.
+     */
+    public StubGroupImpl(String name) {
+        m_name = name;
+        m_properties = new Properties();
+        m_credentials = new Properties();
+        m_basicMembers = new ArrayList();
+        m_requiredMembers = new ArrayList();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean addMember(Role role) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean addMember(String roleName) {
+        return m_basicMembers.add(roleName);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean addRequiredMember(Role role) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean addRequiredMember(String roleName) {
+        return m_requiredMembers.add(roleName);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((m_name == null) ? 0 : m_name.hashCode());
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        StubGroupImpl other = (StubGroupImpl) obj;
+        if (m_name == null) {
+            if (other.m_name != null) {
+                return false;
+            }
+        } else if (!m_name.equals(other.m_name)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Dictionary getCredentials() {
+        return m_credentials;
+    }
+
+    /**
+     * Returns the names of all basic members.
+     * 
+     * @return a list with basic member names (as String!), never <code>null</code>.
+     */
+    public List getMemberNames() {
+        return m_basicMembers;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Role[] getMembers() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getName() {
+        return m_name;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Dictionary getProperties() {
+        return m_properties;
+    }
+
+    /**
+     * Returns the names of all required members.
+     * 
+     * @return a list with required member names (as String!), never <code>null</code>.
+     */
+    public List getRequiredMemberNames() {
+        return m_requiredMembers;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Role[] getRequiredMembers() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getType() {
+        return Role.GROUP;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasCredential(String key, Object value) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean removeMember(Role role) {
+        throw new UnsupportedOperationException();
+    }
+}

Propchange: felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/StubGroupImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/osgi/Activator.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/osgi/Activator.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/osgi/Activator.java (added)
+++ felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/osgi/Activator.java Fri Sep 28 12:58:59 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.felix.useradmin.filestore.osgi;
+
+import java.util.Properties;
+
+import org.apache.felix.useradmin.RoleRepositoryStore;
+import org.apache.felix.useradmin.filestore.RoleRepositoryFileStore;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.useradmin.UserAdminListener;
+
+/**
+ * Registers the {@link RoleRepositoryFileStore} as {@link RoleRepositoryStore} service.
+ */
+public class Activator implements BundleActivator {
+
+    private RoleRepositoryFileStore m_store;
+
+    public void start(BundleContext context) throws Exception {
+        m_store = new RoleRepositoryFileStore(context.getDataFile(""));
+        
+        String[] interfaces = { RoleRepositoryStore.class.getName(), UserAdminListener.class.getName(), ManagedService.class.getName() };
+        
+        Properties props = new Properties();
+        props.put(Constants.SERVICE_PID, RoleRepositoryFileStore.PID);
+
+        context.registerService(interfaces, m_store, props);
+    }
+
+    public void stop(BundleContext context) throws Exception {
+        if (m_store != null) {
+            m_store.stop();
+            m_store = null;
+        }
+    }
+}

Propchange: felix/trunk/useradmin/filestore/src/main/java/org/apache/felix/useradmin/filestore/osgi/Activator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/ResettableTimerTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/ResettableTimerTest.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/ResettableTimerTest.java (added)
+++ felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/ResettableTimerTest.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,201 @@
+/**
+ *  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.useradmin.filestore;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import junit.framework.TestCase;
+
+/**
+ * Test case for {@link ResettableTimer}.
+ */
+public class ResettableTimerTest extends TestCase {
+
+    private ResettableTimer m_timer;
+    private CountDownLatch m_latch;
+
+    /**
+     * Tests that a executor service is mandatory.
+     */
+    public void testExecutorSerivceIsMandatory() throws Exception {
+        Runnable task = createStubTask();
+
+        try {
+            new ResettableTimer(null, task, 10, TimeUnit.HOURS);
+            fail("IllegalArgumentException expected!");
+        } catch (Exception e) {
+            // Ok; expected
+        }
+    }
+
+    /**
+     * Tests that multiple calls to {@link ResettableTimer#schedule()} causes 
+     * pending tasks to be cancelled.
+     */
+    public void testScheduleCancelsPendingTasksOk() throws Exception {
+        final AtomicInteger m_counter = new AtomicInteger(0);
+
+        Runnable task = new Runnable() {
+            public void run() {
+                m_counter.incrementAndGet();
+            }
+        };
+        
+        m_timer = new ResettableTimer(task, 100, TimeUnit.MILLISECONDS);
+        m_timer.schedule();
+
+        TimeUnit.MILLISECONDS.sleep(75);
+        
+        Future f = m_timer.schedule();
+        f.get();
+
+        assertEquals(1, m_counter.get());
+    }
+
+    /**
+     * Test method for {@link org.apache.felix.useradmin.impl.ResettableTimer#schedule()}.
+     */
+    public void testScheduleMultipleTasksOk() throws Exception {
+        final AtomicInteger m_counter = new AtomicInteger(0);
+
+        Runnable task = new Runnable() {
+            public void run() {
+                m_counter.incrementAndGet();
+            }
+        };
+
+        m_timer = new ResettableTimer(task, 100, TimeUnit.MILLISECONDS);
+        
+        Future f = m_timer.schedule();
+        f.get();
+
+        f = m_timer.schedule();
+        f.get();
+
+        assertEquals(2, m_counter.get());
+    }
+
+    /**
+     * Tests that a task is invoked as single shot.
+     */
+    public void testScheduleSingleShotOk() throws Exception {
+        m_latch = new CountDownLatch(1);
+
+        Runnable task = new Runnable() {
+            public void run() {
+                m_latch.countDown();
+            }
+        };
+
+        m_timer = new ResettableTimer(task, 100, TimeUnit.MILLISECONDS);
+        m_timer.schedule();
+
+        assertTrue(m_latch.await(200, TimeUnit.MILLISECONDS));
+    }
+
+    /**
+     * Test method for {@link org.apache.felix.useradmin.impl.ResettableTimer#shutDown()}.
+     */
+    public void testShutDownOk() throws Exception {
+        final AtomicInteger m_counter = new AtomicInteger(0);
+
+        Runnable task = new Runnable() {
+            public void run() {
+                m_counter.incrementAndGet();
+            }
+        };
+        
+        m_timer = new ResettableTimer(task, 100, TimeUnit.MILLISECONDS);
+        m_timer.schedule();
+
+        TimeUnit.MILLISECONDS.sleep(75);
+        
+        m_timer.schedule();
+        m_timer.shutDown();
+
+        assertEquals(1, m_counter.get());
+    }
+
+    /**
+     * Tests that a task is mandatory.
+     */
+    public void testTaskIsMandatory() throws Exception {
+        try {
+            new ResettableTimer(null, 10, TimeUnit.HOURS);
+            fail("IllegalArgumentException expected!");
+        } catch (Exception e) {
+            // Ok; expected
+        }
+    }
+
+    /**
+     * Tests that a timeout cannot be zero.
+     */
+    public void testTimeoutCannotBeNegative() throws Exception {
+        Runnable task = createStubTask();
+
+        try {
+            new ResettableTimer(task, -1, TimeUnit.MILLISECONDS);
+            fail("IllegalArgumentException expected!");
+        } catch (Exception e) {
+            // Ok; expected
+        }
+    }
+
+    /**
+     * Tests that a timeout cannot be zero.
+     */
+    public void testTimeoutCannotBeZero() throws Exception {
+        Runnable task = createStubTask();
+
+        try {
+            new ResettableTimer(task, 0, TimeUnit.MILLISECONDS);
+            fail("IllegalArgumentException expected!");
+        } catch (Exception e) {
+            // Ok; expected
+        }
+    }
+
+    /**
+     * Tests that a time unit is mandatory.
+     */
+    public void testTimeUnitIsMandatory() throws Exception {
+        Runnable task = createStubTask();
+
+        try {
+            new ResettableTimer(task, 10, null);
+            fail("IllegalArgumentException expected!");
+        } catch (Exception e) {
+            // Ok; expected
+        }
+    }
+
+    /**
+     * @return a {@link Runnable} that does nothing, never <code>null</code>.
+     */
+    private Runnable createStubTask() {
+        return new Runnable() {
+            public void run() {
+                // No-op
+            }
+        };
+    }
+}

Propchange: felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/ResettableTimerTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStorePerformanceTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStorePerformanceTest.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStorePerformanceTest.java (added)
+++ felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStorePerformanceTest.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,168 @@
+/**
+ *  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.useradmin.filestore;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.apache.felix.useradmin.impl.RoleRepository;
+import org.apache.felix.useradmin.impl.role.GroupImpl;
+import org.apache.felix.useradmin.impl.role.UserImpl;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+/**
+ * Test case for {@link RoleRepositorySerializer}. 
+ */
+public class RoleRepositoryFileStorePerformanceTest extends TestCase {
+
+    private static final int USER_COUNT = 15000;
+    private static final int GROUP_COUNT = 500;
+
+    private Group[] m_groups;
+    private User[] m_users;
+    
+    private Map m_repository;
+    private RoleRepositoryFileStore m_store;
+
+    /**
+     * Executes the performance test.
+     */
+    public void testPerformanceOk() throws Exception {
+        allocateMemory();
+
+        writeRepositoryPerformanceTest();
+
+        releaseMemory();
+        
+        readRepositoryPerformanceTest();
+    }
+
+    /**
+     * Does a very simple performance test for a large number of users spread over several groups.
+     */
+    protected void readRepositoryPerformanceTest() throws Exception {
+        long r_st = System.currentTimeMillis();
+        Map result = m_store.retrieve();
+        long r_time = System.currentTimeMillis() - r_st;
+
+        assertNotNull(result);
+        assertEquals(GROUP_COUNT + USER_COUNT + 1, result.size());
+
+        System.out.println("Read time : " + (r_time / 1000.0) + "s.");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        m_store = new RoleRepositoryFileStore(new File(System.getProperty("java.io.tmpdir")), false /* disable background writes */);
+
+        m_repository = new HashMap(USER_COUNT + GROUP_COUNT + 1);
+
+        addToRepository(RoleRepository.USER_ANYONE);
+    }
+    
+    /**
+     * Does a very simple performance test for writing a large number of users spread over several groups.
+     */
+    protected void writeRepositoryPerformanceTest() throws Exception {
+        long w_st = System.currentTimeMillis();
+        m_store.store(m_repository);
+        long w_time = System.currentTimeMillis() - w_st;
+
+        System.out.println("Write time: " + (w_time / 1000.0) + "s.");
+    }
+
+    private void addToRepository(Role role) {
+        m_repository.put(role.getName(), role);
+    }
+
+    /**
+     * 
+     */
+    private void allocateMemory() {
+        m_groups = new Group[GROUP_COUNT];
+        for (int i = 0; i < m_groups.length; i++) {
+            m_groups[i] = createGroup(i+1);
+            m_groups[i].addRequiredMember(RoleRepository.USER_ANYONE);
+
+            addToRepository(m_groups[i]);
+        }
+
+        m_users = new User[USER_COUNT];
+        for (int i = 0; i < m_users.length; i++) {
+            m_users[i] = createUser(i+1);
+            
+            int groupIdx = (i % m_groups.length);
+            m_groups[groupIdx].addMember(m_users[i]);
+            
+            addToRepository(m_users[i]);
+        }
+    }
+
+    private Group createGroup(int idx) {
+        String name = "Group" + idx;
+        
+        Group result = new GroupImpl(name);
+
+        setCredentials(result);
+        setProperties(result);
+        
+        return result;
+    }
+
+    private User createUser(int idx) {
+        String name = "User" + idx;
+        
+        User result = new UserImpl(name);
+
+        setCredentials(result);
+        setProperties(result);
+        
+        return result;
+    }
+
+    /**
+     * 
+     */
+    private void releaseMemory() {
+        m_groups = null;
+        m_users = null;
+
+        System.gc();
+        System.gc();
+        System.gc();
+    }
+
+    private void setCredentials(User user) {
+        user.getCredentials().put("name", user.getName());
+        user.getCredentials().put("password", "secret");
+        user.getCredentials().put("certificate", new byte[] { (byte) 0x55, (byte) 0xAA } );
+    }
+    
+    private void setProperties(Role role) {
+        role.getProperties().put("key1", "value1");
+        role.getProperties().put("key2", "hello world".getBytes());
+    }
+}

Propchange: felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStorePerformanceTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStoreTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStoreTest.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStoreTest.java (added)
+++ felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStoreTest.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,189 @@
+/**
+ *  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.useradmin.filestore;
+
+import java.io.File;
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+import org.osgi.service.cm.ConfigurationException;
+
+/**
+ * Test cases for {@link RoleRepositoryFileStore}.
+ */
+public class RoleRepositoryFileStoreTest extends TestCase {
+
+    private RoleRepositoryFileStore m_store;
+    
+    /**
+     * Tests that calling updated without the key "background.write.disabled" fails.
+     */
+    public void testUpdateConfigurationWithoutKeyWriteDisabledFail() throws Exception {
+        try {
+            m_store.updated(new Properties());
+            fail("ConfigurationException expected!");
+        } catch (ConfigurationException e) {
+            // Ok; expected
+        }
+    }
+    
+    /**
+     * Tests that calling updated with the key "background.write.disabled" set to "false" succeeds.
+     */
+    public void testUpdateConfigurationWithKeyWriteDisabledOk() throws Exception {
+        Properties properties = new Properties();
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DISABLED, "true");
+
+        m_store.updated(properties);
+    }
+    
+    /**
+     * Tests that calling updated with the key "background.write.disabled" set to a numeric value fails.
+     */
+    public void testUpdateConfigurationWithKeyWriteDisabledInvalidValueFail() throws Exception {
+        Properties properties = new Properties();
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DISABLED, Integer.valueOf(1));
+
+        try {
+            m_store.updated(properties);
+            fail("ConfigurationException expected!");
+        } catch (ConfigurationException e) {
+            // Ok; expected
+        }
+    }
+
+    /**
+     * Tests that calling updated without the key "background.write.delay.value" fails.
+     */
+    public void testUpdateConfigurationWithoutKeyWriteDelayValueFail() throws Exception {
+        Properties properties = new Properties();
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DISABLED, "false");
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DELAY_TIMEUNIT, "seconds");
+
+        try {
+            m_store.updated(properties);
+            fail("ConfigurationException expected!");
+        } catch (ConfigurationException e) {
+            // Ok; expected
+        }
+    }
+
+    /**
+     * Tests that calling updated with the key "background.write.delay.value" set to a non-numeric value fails.
+     */
+    public void testUpdateConfigurationWithKeyWriteDelayValueInvalidValueTypeFail() throws Exception {
+        Properties properties = new Properties();
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DISABLED, "false");
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DELAY_VALUE, "seconds");
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DELAY_TIMEUNIT, "seconds");
+
+        try {
+            m_store.updated(properties);
+            fail("ConfigurationException expected!");
+        } catch (ConfigurationException e) {
+            // Ok; expected
+        }
+    }
+
+    /**
+     * Tests that calling updated with the key "background.write.delay.value" set to zero fails.
+     */
+    public void testUpdateConfigurationWithKeyWriteDelayValueZeroValueFail() throws Exception {
+        Properties properties = new Properties();
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DISABLED, "false");
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DELAY_VALUE, "0");
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DELAY_TIMEUNIT, "seconds");
+
+        try {
+            m_store.updated(properties);
+            fail("ConfigurationException expected!");
+        } catch (ConfigurationException e) {
+            // Ok; expected
+        }
+    }
+
+    /**
+     * Tests that calling updated with the key "background.write.delay.value" set to a negative value fails.
+     */
+    public void testUpdateConfigurationWithKeyWriteDelayValueNegativeValueFail() throws Exception {
+        Properties properties = new Properties();
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DISABLED, "false");
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DELAY_VALUE, "-1");
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DELAY_TIMEUNIT, "seconds");
+
+        try {
+            m_store.updated(properties);
+            fail("ConfigurationException expected!");
+        } catch (ConfigurationException e) {
+            // Ok; expected
+        }
+    }
+
+    /**
+     * Tests that calling updated without the key "background.write.delay.timeunit" succeeds as it is optional.
+     */
+    public void testUpdateConfigurationWithoutKeyWriteDelayTimeUnitOk() throws Exception {
+        Properties properties = new Properties();
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DISABLED, "false");
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DELAY_VALUE, "1");
+
+        m_store.updated(properties);
+    }
+
+    /**
+     * Tests that calling updated with the key "background.write.delay.timeunit" set to an invalid value fails.
+     */
+    public void testUpdateConfigurationWithKeyWriteDelayTimeUnitInvalidValueFail() throws Exception {
+        Properties properties = new Properties();
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DISABLED, "false");
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DELAY_VALUE, "1");
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DELAY_TIMEUNIT, "1");
+
+        try {
+            m_store.updated(properties);
+            fail("ConfigurationException expected!");
+        } catch (ConfigurationException e) {
+            // Ok; expected
+        }
+    }
+
+    /**
+     * Tests that calling updated with all keys succeeds.
+     */
+    public void testUpdateConfigurationWithAllKeysOk() throws Exception {
+        Properties properties = new Properties();
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DISABLED, "false");
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DELAY_VALUE, "1");
+        properties.put(RoleRepositoryFileStore.KEY_WRITE_DELAY_TIMEUNIT, "seconds");
+
+        m_store.updated(properties);
+    }
+    
+    /**
+     * Tests that calling updated with a <code>null</code>-dictionary causes the default settings to be applied.
+     */
+    public void testUpdateConfigurationWithoutPropertiesOk() throws Exception {
+        m_store.updated(null);
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        m_store = new RoleRepositoryFileStore(new File(System.getProperty("java.io.tmpdir")), false /* disable background writes */);
+    }
+}

Propchange: felix/trunk/useradmin/filestore/src/test/java/org/apache/felix/useradmin/filestore/RoleRepositoryFileStoreTest.java
------------------------------------------------------------------------------
    svn:eol-style = native