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 [3/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...

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

Added: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/RoleFactory.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/RoleFactory.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/RoleFactory.java (added)
+++ felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/RoleFactory.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,57 @@
+/**
+ *  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;
+
+
+import org.apache.felix.useradmin.impl.role.GroupImpl;
+import org.apache.felix.useradmin.impl.role.RoleImpl;
+import org.apache.felix.useradmin.impl.role.UserImpl;
+import org.osgi.service.useradmin.Role;
+
+/**
+ * Provides a factory for creating the various role instances, can be used by external 
+ * implementations to create new role instances.
+ */
+public final class RoleFactory {
+
+    /**
+     * Creates a new instance of {@link RoleFactory}, not used.
+     */
+    private RoleFactory() {
+        // Nop
+    }
+
+    /**
+     * Creates a new role instance.
+     * 
+     * @param type the type of the role to create;
+     * @param name the name of the role to create.
+     * @return a new {@link RoleImpl} instance denoting the requested role, never <code>null</code>.
+     */
+    public static Role createRole(int type, String name) {
+        if (type == Role.USER) {
+            UserImpl result = new UserImpl(name);
+            return result;
+        } else if (type == Role.GROUP) {
+            GroupImpl result = new GroupImpl(name);
+            return result;
+        } else {
+            RoleImpl result = new RoleImpl(name);
+            return result;
+        }
+    }
+}

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

Added: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/RoleRepositoryStore.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/RoleRepositoryStore.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/RoleRepositoryStore.java (added)
+++ felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/RoleRepositoryStore.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,94 @@
+/**
+ *  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;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+import org.osgi.service.useradmin.Role;
+
+/**
+ * Provides an abstraction to store and retrieve a role repository.
+ */
+public interface RoleRepositoryStore extends Closeable {
+    
+    /**
+     * Adds a given role to this backend.
+     * <p>
+     * If the given role is already contained by this backed, this method 
+     * should not do anything and return <code>false</code> to denote this.
+     * </p>
+     * 
+     * @param role the role to add, cannot be <code>null</code>.
+     * @return <code>true</code> if the role was successfully added, <code>false</code> otherwise.
+     * @throws IllegalArgumentException in case the given argument was <code>null</code>;
+     * @throws IOException in case of I/O problems.
+     */
+    boolean addRole(Role role) throws IOException;
+
+    /**
+     * Closes this store, allowing implementations to free up resources, close
+     * connections, and so on.
+     * 
+     * @throws IOException in case of I/O problems.
+     */
+    void close() throws IOException;
+
+    /**
+     * Returns all available roles in this backend.
+     * 
+     * @return an array with all roles, never <code>null</code>, but can be empty.
+     * @throws IOException in case of I/O problems.
+     */
+    Role[] getAllRoles() throws IOException;
+    
+    /**
+     * Returns a {@link Role} by its name.
+     * 
+     * @param roleName the name of the role to return, cannot be <code>null</code>.
+     * @return the role with the given name, or <code>null</code> if no such role exists.
+     * @throws IllegalArgumentException in case the given argument was <code>null</code>;
+     * @throws IOException in case of I/O problems.
+     */
+    Role getRoleByName(String roleName) throws IOException;
+
+    /**
+     * Called once before any other method of this interface is being called.
+     * <p>
+     * Implementations can use this method to create a connection to the 
+     * backend, or load the initial set of roles, and so on.
+     * </p>
+     * 
+     * @throws IOException in case of I/O problems.
+     */
+    void initialize() throws IOException;
+
+    /**
+     * Removes a given role from this backend.
+     * <p>
+     * If the given role is not contained by this backed, this method 
+     * should not do anything and return <code>false</code> to denote this.
+     * </p>
+     * 
+     * @param role the role to remove, cannot be <code>null</code>.
+     * @return <code>true</code> if the role was successfully removed, <code>false</code> otherwise.
+     * @throws IllegalArgumentException in case the given argument was <code>null</code>;
+     * @throws IOException in case of I/O problems.
+     */
+    boolean removeRole(Role role) throws IOException;
+    
+}

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

Added: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/AuthorizationImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/AuthorizationImpl.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/AuthorizationImpl.java (added)
+++ felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/AuthorizationImpl.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,97 @@
+/**
+ *  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.impl;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+
+import org.apache.felix.useradmin.impl.role.RoleImpl;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+/**
+ * Provides an implementation for {@link Authorization}.
+ */
+public class AuthorizationImpl implements Authorization {
+
+    private final String m_name;
+    private final User m_user;
+    private final RoleRepository m_roleManager;
+    private final RoleChecker m_roleChecker;
+
+    /**
+     * Creates a new {@link AuthorizationImpl} instance for the given {@link User}.
+     * 
+     * @param roleManager the role manager to use for obtaining the roles, cannot be <code>null</code>.
+     */
+    public AuthorizationImpl(RoleRepository roleManager) {
+        this(null, roleManager);
+    }
+
+    /**
+     * Creates a new {@link AuthorizationImpl} instance for the given {@link User}.
+     * 
+     * @param user the {@link User} to authorize, may be <code>null</code> for the anonymous user;
+     * @param roleManager the role manager to use for obtaining the roles, cannot be <code>null</code>.
+     */
+    public AuthorizationImpl(User user, RoleRepository roleManager) {
+        m_user = user;
+        m_roleManager = roleManager;
+        m_name = (user != null) ? user.getName() : null;
+        m_roleChecker = new RoleChecker();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getName() {
+        return m_name;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasRole(String name) {
+        Role role = m_roleManager.getRoleByName(name);
+        if (role == null) {
+            // No role found, so it is never implied...
+            return false;
+        }
+        return m_roleChecker.isImpliedBy(role, m_user);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] getRoles() {
+        List result = new ArrayList();
+
+        Iterator rolesIter = m_roleManager.getRoles(null /* filter */).iterator();
+        while (rolesIter.hasNext()) {
+            RoleImpl role = (RoleImpl) rolesIter.next();
+            if (!Role.USER_ANYONE.equals(role.getName()) && m_roleChecker.isImpliedBy(role, m_user)) {
+                result.add(role.getName());
+            }
+        }
+
+        return result.isEmpty() ? null : (String[]) result.toArray(new String[result.size()]);
+    }
+}

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

Added: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/EventDispatcher.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/EventDispatcher.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/EventDispatcher.java (added)
+++ felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/EventDispatcher.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,226 @@
+/**
+ *  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.impl;
+
+import java.util.Properties;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.UserAdminEvent;
+import org.osgi.service.useradmin.UserAdminListener;
+
+/**
+ * Provides an event dispatcher for delivering {@link UserAdminEvent}s asynchronously. 
+ */
+public final class EventDispatcher implements Runnable {
+    
+    private static final String TOPIC_BASE = "org/osgi/service/useradmin/UserAdmin/";
+
+    private final EventAdmin m_eventAdmin;
+    private final UserAdminListenerList m_listenerList;
+    private final BlockingQueue m_eventQueue;
+    private final Thread m_backgroundThread;
+
+    /**
+     * Creates a new {@link EventDispatcher} instance, and starts a background thread to deliver all events.
+     * 
+     * @param eventAdmin the event admin to use, cannot be <code>null</code>;
+     * @param listenerList the list with {@link UserAdminListener}s, cannot be <code>null</code>.
+     * @throws IllegalArgumentException in case one of the given parameters was <code>null</code>.
+     */
+    public EventDispatcher(EventAdmin eventAdmin, UserAdminListenerList listenerList) {
+        if (eventAdmin == null) {
+            throw new IllegalArgumentException("EventAdmin cannot be null!");
+        }
+        if (listenerList == null) {
+            throw new IllegalArgumentException("ListenerList cannot be null!");
+        }
+
+        m_eventAdmin = eventAdmin;
+        m_listenerList = listenerList;
+        m_eventQueue = new LinkedBlockingQueue();
+
+        m_backgroundThread = new Thread(this, "UserAdmin event dispatcher");
+    }
+
+    /**
+     * Dispatches a given event for asynchronous delivery to all interested listeners, 
+     * including those using the {@link EventAdmin} service.
+     * <p>
+     * This method will perform a best-effort to dispatch the event to all listeners, i.e., 
+     * there is no guarantee that the listeners will actually obtain the event, nor any
+     * notification is given in case delivery fails.
+     * </p>
+     * 
+     * @param event the event to dispatch, cannot be <code>null</code>.
+     * @throws IllegalStateException in case this dispatcher is already stopped.
+     */
+    public void dispatch(UserAdminEvent event) {
+        if (!isRunning()) {
+            return;
+        }
+
+        try {
+            m_eventQueue.put(event);
+        } catch (InterruptedException e) {
+            // Restore interrupt flag...
+            Thread.currentThread().interrupt();
+        }
+    }
+    
+    /**
+     * Starts this event dispatcher, allowing it to pick up events and deliver them.
+     */
+    public void start() {
+        if (!isRunning()) {
+            m_backgroundThread.start();
+        }
+    }
+
+    /**
+     * Signals this event dispatcher to stop its work and clean up all running threads.
+     */
+    public void stop() {
+        if (!isRunning()) {
+            return;
+        }
+
+        // Add poison object to queue to let the background thread terminate...
+        m_eventQueue.add(EventDispatcher.this);
+
+        try {
+            m_backgroundThread.join();
+        } catch (InterruptedException e) {
+            // We're already stopping; so don't bother... 
+        }
+    }
+
+    /**
+     * Returns whether or not the background thread is running.
+     * 
+     * @return <code>true</code> if the background thread is running (alive), <code>false</code> otherwise.
+     */
+    final boolean isRunning() {
+        return m_backgroundThread.isAlive();
+    }
+    
+    /**
+     * Provides the main event loop, which waits until an event is enqueued in order 
+     * to deliver it to any interested listener.
+     */
+    public void run() {
+        try {
+            while (true) {
+                // Blocks until a event is dispatched...
+                Object event = m_eventQueue.take();
+
+                if (event instanceof UserAdminEvent) {
+                    // Got a "normal" user admin event; lets dispatch it further...
+                    deliverEventSynchronously((UserAdminEvent) event);
+                } else {
+                    // Got a "poison" object; this means we must stop running...
+                    return;
+                }
+            }
+        } catch (InterruptedException e) {
+            // Restore interrupt flag, and terminate thread...
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    /**
+     * Converts a given {@link UserAdminEvent} to a {@link Event} that can be
+     * dispatched through the {@link EventAdmin} service.
+     * 
+     * @param event
+     *            the event to convert, cannot be <code>null</code>.
+     * @return a new {@link Event} instance containing the same set of
+     *         information as the given event, never <code>null</code>.
+     */
+    private Event convertEvent(UserAdminEvent event) {
+        String topic = getTopicName(event.getType());
+        Role role = event.getRole();
+        ServiceReference serviceRef = event.getServiceReference();
+
+        Properties props = new Properties();
+        props.put(EventConstants.EVENT_TOPIC, TOPIC_BASE.concat(topic));
+        props.put(EventConstants.EVENT, event);
+        props.put("role", role);
+        props.put("role.name", role.getName());
+        props.put("role.type", new Integer(role.getType()));
+        if (serviceRef != null) {
+            props.put(EventConstants.SERVICE, serviceRef);
+            Object property;
+            
+            property = serviceRef.getProperty(Constants.SERVICE_ID);
+            if (property != null) {
+                props.put(EventConstants.SERVICE_ID, property);
+            }
+            property = serviceRef.getProperty(Constants.OBJECTCLASS);
+            if (property != null) {
+                props.put(EventConstants.SERVICE_OBJECTCLASS, property);
+            }
+            property = serviceRef.getProperty(Constants.SERVICE_PID);
+            if (property != null) {
+                props.put(EventConstants.SERVICE_PID, property);
+            }
+        }
+
+        return new Event(topic, props);
+    }
+
+    /**
+     * Delivers the given event synchronously to all interested listeners.
+     * 
+     * @param event the event to deliver, cannot be <code>null</code>.
+     */
+    private void deliverEventSynchronously(UserAdminEvent event) {
+        // Asynchronously deliver an event to the EventAdmin service...
+        m_eventAdmin.postEvent(convertEvent(event));
+
+        // Synchronously call all UserAdminListeners to deliver the event...
+        UserAdminListener[] listeners = m_listenerList.getListeners();
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].roleChanged(event);
+        }
+    }
+    
+    /**
+     * Converts a topic name for the given event-type.
+     * 
+     * @param type the type of event to get the topic name for.
+     * @return a topic name, never <code>null</code>.
+     */
+    private String getTopicName(int type) {
+        switch (type) {
+            case UserAdminEvent.ROLE_CREATED:
+                return "ROLE_CREATED";
+            case UserAdminEvent.ROLE_CHANGED:
+                return "ROLE_CHANGED";
+            case UserAdminEvent.ROLE_REMOVED:
+                return "ROLE_REMOVED";
+            default:
+                return null;
+        }
+    }
+}

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

Added: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/RoleChangeListener.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/RoleChangeListener.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/RoleChangeListener.java (added)
+++ felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/RoleChangeListener.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,66 @@
+/**
+ *  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.impl;
+
+import org.osgi.service.useradmin.Role;
+
+/**
+ * Provides a callback for listening to role changes.
+ */
+public interface RoleChangeListener {
+
+    /**
+     * Called when a new role is added.
+     * 
+     * @param role the role that is added.
+     */
+    void roleAdded(Role role);
+    
+    /**
+     * Called when a role is removed.
+     * 
+     * @param role the role that is removed.
+     */
+    void roleRemoved(Role role);
+    
+    /**
+     * Called when a new property-entry is added to a role.
+     * 
+     * @param role the role that changed;
+     * @param key the key of the entry;
+     * @param value the value associated to the key.
+     */
+    void propertyAdded(Role role, Object key, Object value);
+
+    /**
+     * Called when an property-entry is removed from a role.
+     * 
+     * @param role the role that changed;
+     * @param key the key of the entry.
+     */
+    void propertyRemoved(Role role, Object key);
+
+    /**
+     * Called when an property-entry is changed for a role.
+     * 
+     * @param role the role that changed;
+     * @param key the key of the entry;
+     * @param oldValue the old value associated to the key;
+     * @param newValue the new value associated to the key.
+     */
+    void propertyChanged(Role role, Object key, Object oldValue, Object newValue);
+}
\ No newline at end of file

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

Added: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/RoleChecker.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/RoleChecker.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/RoleChecker.java (added)
+++ felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/RoleChecker.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,132 @@
+/**
+ *  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.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+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;
+
+/**
+ * Helper class to check for implied role memberships.
+ */
+final class RoleChecker {
+
+    /**
+     * Verifies whether the given role is implied by the memberships of the given user.
+     * 
+     * @param user the user to check the roles for, cannot be <code>null</code>;
+     * @param impliedRole the implied role to check for, cannot be <code>null</code>.
+     * @return <code>true</code> if the given user has the implied role, <code>false</code> otherwise.
+     */
+    public boolean isImpliedBy(Role role, Role impliedRole) {
+        if (role instanceof Group) {
+            return isGroupImpliedBy((Group) role, impliedRole, new ArrayList());
+        } else if (role instanceof User) {
+            return isUserImpliedBy((User) role, impliedRole);
+        } else {
+            return isRoleImpliedBy(role, impliedRole);
+        }
+    }
+
+    /**
+     * Verifies whether the given group is implied by the given role.
+     * 
+     * @param group the group to check, cannot be <code>null</code>;
+     * @param impliedRole the implied role to check for, cannot be <code>null</code>;
+     * @param seenGroups a list of all seen groups, used for detecting cycles in groups, cannot be <code>null</code>.
+     * @return <code>true</code> if the given group has the implied role, <code>false</code> otherwise.
+     */
+    private boolean isGroupImpliedBy(Group group, Role impliedRole, List seenGroups) {
+        Role[] basicRoles = group.getMembers();
+        Role[] requiredRoles = group.getRequiredMembers();
+
+        boolean isImplied = true;
+        
+        // Check whether all required roles are implied...
+        for (int i = 0; (requiredRoles != null) && isImplied && (i < requiredRoles.length); i++) {
+            Role requiredRole = requiredRoles[i];
+            if (seenGroups.contains(requiredRole)) {
+                // Found a cycle between groups; always yield false!
+                return false;
+            }
+            
+            if (requiredRole instanceof Group) {
+                seenGroups.add(requiredRole);
+                isImplied = isGroupImpliedBy((Group) requiredRole, impliedRole, seenGroups);
+            } else if (requiredRole instanceof User) {
+                isImplied  = isUserImpliedBy((User) requiredRole, impliedRole);
+            } else /* if (requiredRoles[i] instanceof RoleImpl) */ {
+                isImplied = isRoleImpliedBy(requiredRole, impliedRole);
+            }
+        }
+
+        // Required role is not implied by the given role; we can stop now...
+        if (!isImplied) {
+            return false;
+        }
+
+        // Ok; all required roles are implied, let's verify whether a least one basic role is implied...
+        isImplied = false;
+
+        // Check whether at least one basic role is implied...
+        for (int i = 0; (basicRoles != null) && !isImplied && (i < basicRoles.length); i++) {
+            Role basicRole = (Role) basicRoles[i];
+            if (seenGroups.contains(basicRole)) {
+                // Found a cycle between groups; always yield false!
+                return false;
+            }
+
+            if (basicRole instanceof Group) {
+                seenGroups.add(basicRole);
+                isImplied = isGroupImpliedBy((Group) basicRole, impliedRole, seenGroups);
+            } else if (basicRole instanceof UserImpl) {
+                isImplied = isUserImpliedBy((User) basicRole, impliedRole);
+            } else /* if (requiredRoles[i] instanceof RoleImpl) */ {
+                isImplied = isRoleImpliedBy(basicRole, impliedRole);
+            }
+        }
+
+        return isImplied;
+    }
+
+    /**
+     * Verifies whether the given user is implied by the given role.
+     * 
+     * @param user the user to check, cannot be <code>null</code>;
+     * @param impliedRole the implied role to check for, cannot be <code>null</code>;
+     * @return <code>true</code> if the given user is implied by the given role, <code>false</code> otherwise.
+     */
+    private boolean isUserImpliedBy(User user, Role impliedRole) {
+        return Role.USER_ANYONE.equals(user.getName()) || (impliedRole != null && impliedRole.getName().equals(user.getName()));
+    }
+
+    /**
+     * Verifies whether the given role is implied by the given role.
+     * 
+     * @param role the role to check, cannot be <code>null</code>;
+     * @param impliedRole the implied role to check for, cannot be <code>null</code>;
+     * @return <code>true</code> if the given role is implied by the given role, <code>false</code> otherwise.
+     */
+    private boolean isRoleImpliedBy(Role role, Role impliedRole) {
+        return Role.USER_ANYONE.equals(role.getName()) || (impliedRole != null && impliedRole.getName().equals(role.getName()));
+    }
+}

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

Added: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/RoleRepository.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/RoleRepository.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/RoleRepository.java (added)
+++ felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/RoleRepository.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,367 @@
+/**
+ *  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.impl;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.felix.useradmin.BackendException;
+import org.apache.felix.useradmin.RoleFactory;
+import org.apache.felix.useradmin.RoleRepositoryStore;
+import org.apache.felix.useradmin.impl.role.RoleImpl;
+import org.osgi.framework.Filter;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.UserAdminPermission;
+
+/**
+ * Provides a manager and entry-point for accessing {@link Role}s.
+ */
+public final class RoleRepository {
+
+    /**
+     * Hands off all obtained role change event to a local set of listeners.
+     */
+    final class RoleChangeReflector implements RoleChangeListener {
+        /**
+         * {@inheritDoc}
+         */
+        public void roleAdded(Role role) {
+            Iterator iterator = createListenerIterator();
+            while (iterator.hasNext()) {
+                ((RoleChangeListener) iterator.next()).roleAdded(role);
+            }
+        }
+        
+        /**
+         * {@inheritDoc}
+         */
+        public void roleRemoved(Role role) {
+            Iterator iterator = createListenerIterator();
+            while (iterator.hasNext()) {
+                ((RoleChangeListener) iterator.next()).roleRemoved(role);
+            }
+        }
+        
+        /**
+         * {@inheritDoc}
+         */
+        public void propertyAdded(Role role, Object key, Object value) {
+            Iterator iterator = createListenerIterator();
+            while (iterator.hasNext()) {
+                ((RoleChangeListener) iterator.next()).propertyAdded(role, key, value);
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void propertyRemoved(Role role, Object key) {
+            Iterator iterator = createListenerIterator();
+            while (iterator.hasNext()) {
+                ((RoleChangeListener) iterator.next()).propertyRemoved(role, key);
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void propertyChanged(Role role, Object key, Object oldValue, Object newValue) {
+            Iterator iterator = createListenerIterator();
+            while (iterator.hasNext()) {
+                ((RoleChangeListener) iterator.next()).propertyChanged(role, key, oldValue, newValue);
+            }
+        }
+    }
+
+    /** The single predefined role. */
+    public static final Role USER_ANYONE = RoleFactory.createRole(Role.ROLE, Role.USER_ANYONE);
+
+    private final RoleRepositoryStore m_store;
+    private final CopyOnWriteArrayList m_listeners;
+    private final RoleChangeReflector m_roleChangeReflector;
+    
+    /**
+     * Creates a new {@link RoleRepository} instance.
+     * 
+     * @param store the {@link RoleRepositoryStore} to use, cannot be <code>null</code>.
+     */
+    public RoleRepository(RoleRepositoryStore store) {
+        m_store = store;
+        
+        m_listeners = new CopyOnWriteArrayList();
+        m_roleChangeReflector = new RoleChangeReflector();
+    }
+
+    /**
+     * Adds a given role to this manager.
+     * 
+     * @param role the role to add, cannot be <code>null</code>. If it is already contained by this manager, this method will not do anything.
+     * @return the given role if added, <code>null</code> otherwise.
+     */
+    public Role addRole(Role role) {
+        if (role == null) {
+            throw new IllegalArgumentException("Role cannot be null!");
+        }
+        if (!(role instanceof RoleImpl)) {
+            throw new IllegalArgumentException("Invalid role type!");
+        }
+
+        checkPermissions();
+
+        try {
+            if (m_store.addRole(role)) {
+                m_roleChangeReflector.roleAdded(role);
+                return wireChangeListener(role);
+            }
+
+            return null;
+        }
+        catch (IOException e) {
+            throw new BackendException("Adding role " + role.getName() + " failed!", e);
+        }
+    }
+
+    /**
+     * Adds the given role change listener to be called for upcoming changes in roles.
+     * 
+     * @param listener the listener to register, cannot be <code>null</code>.
+     * @throws IllegalArgumentException in case the given listener was <code>null</code>.
+     */
+    public void addRoleChangeListener(RoleChangeListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("RoleChangeListener cannot be null!");
+        }
+
+        m_listeners.addIfAbsent(listener);
+    }
+
+    /**
+     * Returns the by its given name.
+     * 
+     * @param roleName the name of the role to return, cannot be <code>null</code>.
+     * @return the role matching the given name, or <code>null</code> if no role matched the given name.
+     */
+    public Role getRoleByName(String roleName) {
+        try {
+            return wireChangeListener(m_store.getRoleByName(roleName));
+        }
+        catch (IOException e) {
+            throw new BackendException("Failed to get role by name: " + roleName + "!", e);
+        }
+    }
+
+    /**
+     * Returns a collection with all roles matching a given filter.
+     * 
+     * @param filter the filter to match the individual roles against, can be <code>null</code> if all roles should be returned.
+     * @return a list with all matching roles, can be empty, but never <code>null</code>.
+     */
+    public List getRoles(Filter filter) {
+        List matchingRoles = new ArrayList();
+
+        try {
+            Role[] roles = m_store.getAllRoles();
+            for (int i = 0; i < roles.length; i++) {
+                Role role = roles[i];
+                if (!isPredefinedRole(role) && ((filter == null) || filter.match(role.getProperties()))) {
+                    matchingRoles.add(wireChangeListener(role));
+                }
+            }
+        }
+        catch (IOException e) {
+            throw new BackendException("Failed to get roles!", e);
+        }
+
+        return matchingRoles;
+    }
+
+    /**
+     * Returns a collection with all roles matching a given key-value pair.
+     * 
+     * @param key the key to search for;
+     * @param value the value to search for.
+     * @return a list with all matching roles, can be empty, but never <code>null</code>.
+     */
+    public List getRoles(String key, String value) {
+        if (key == null) {
+            throw new IllegalArgumentException("Key cannot be null!");
+        }
+        if (value == null) {
+            throw new IllegalArgumentException("Value cannot be null!");
+        }
+
+        List matchingRoles = new ArrayList();
+
+        try {
+            Role[] roles = m_store.getAllRoles();
+            for (int i = 0; i < roles.length; i++) {
+                Role role = roles[i];
+                Dictionary dict = role.getProperties();
+                if (!isPredefinedRole(role) && value.equals(dict.get(key))) {
+                    matchingRoles.add(wireChangeListener(role));
+                }
+            }
+        }
+        catch (IOException e) {
+            throw new BackendException("Failed to get roles!", e);
+        }
+
+        return matchingRoles;
+    }
+
+    /**
+     * Removes a given role from this manager.
+     * 
+     * @param role the role to remove, cannot be <code>null</code>.
+     * @return <code>true</code> if the role was removed (i.e., it was managed by this manager), or <code>false</code> if it was not found.
+     */
+    public boolean removeRole(Role role) {
+        if (role == null) {
+            throw new IllegalArgumentException("Role cannot be null!");
+        }
+        if (!(role instanceof RoleImpl)) {
+            throw new IllegalArgumentException("Invalid role type!");
+        }
+
+        checkPermissions();
+
+        // Cannot remove predefined roles...
+        if (isPredefinedRole(role)) {
+            return false;
+        }
+
+        try {
+            if (m_store.removeRole(role)) {
+                unwireChangeListener(role);
+                m_roleChangeReflector.roleRemoved(role);
+                
+                return true;
+            }
+
+            return false;
+        }
+        catch (IOException e) {
+            throw new BackendException("Failed to remove role " + role.getName() + "!", e);
+        }
+    }
+
+    /**
+     * Removes the given role change listener from be called for changes in roles.
+     * 
+     * @param listener the listener to unregister, cannot be <code>null</code>.
+     * @throws IllegalArgumentException in case the given listener was <code>null</code>.
+     */
+    public void removeRoleChangeListener(RoleChangeListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("RoleChangeListener cannot be null!");
+        }
+
+        m_listeners.remove(listener);
+    }
+
+    /**
+     * Starts this repository.
+     */
+    public void start() {
+        try {
+            // The sole predefined role we've got...
+            m_store.addRole(USER_ANYONE);
+
+            m_store.initialize();
+        }
+        catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+    
+    /**
+     * Stops this repository, allowing it to clean up.
+     */
+    public void stop() {
+        try {
+            m_store.close();
+        }
+        catch (IOException e) {
+            // Ignore; nothing we can do about this here...
+        }
+    }
+
+    /**
+     * Creates a new iterator for iterating over all listeners.
+     * 
+     * @return a new {@link Iterator} instance, never <code>null</code>. 
+     */
+    final Iterator createListenerIterator() {
+        return m_listeners.iterator();
+    }
+
+    /**
+     * Verifies whether the caller has the right permissions to add or remove roles.
+     * 
+     * @throws SecurityException in case the caller has not the right permissions to perform the action.
+     */
+    private void checkPermissions() throws SecurityException {
+        SecurityManager securityManager = System.getSecurityManager();
+        if (securityManager != null) {
+            securityManager.checkPermission(new UserAdminPermission(UserAdminPermission.ADMIN, null));
+        }
+    }
+    
+    /**
+     * Returns whether or not the given role is a predefined role.
+     * <p>
+     * Currently, there's only a single predefined role: {@link Role#USER_ANYONE}.
+     * </p>
+     * 
+     * @param role the role to check, may be <code>null</code>.
+     * @return <code>true</code> if the given role is predefined, <code>false</code> otherwise.
+     */
+    private boolean isPredefinedRole(Role role) {
+        return Role.USER_ANYONE.equals(role.getName());
+    }
+
+    /**
+     * Wires the given role to this repository so it can listen for its changes.
+     * 
+     * @param role the role to listen for its changes, cannot be <code>null</code>.
+     * @return the given role.
+     * @throws IllegalArgumentException in case the given object was not a {@link RoleImpl} instance.
+     */
+    private Role wireChangeListener(Object role) {
+        RoleImpl result = (RoleImpl) role;
+        if (result != null) {
+            result.setRoleChangeListener(m_roleChangeReflector);
+        }
+        return result;
+    }
+
+    /**
+     * Unwires the given role to this repository so it no longer listens for its changes.
+     * 
+     * @param role the role to unwire, cannot be <code>null</code>.
+     * @throws IllegalArgumentException in case the given object was not a {@link RoleImpl} instance.
+     */
+    private void unwireChangeListener(Object role) {
+        RoleImpl result = (RoleImpl) role;
+        result.setRoleChangeListener(null);
+    }
+}

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

Added: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/UserAdminImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/UserAdminImpl.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/UserAdminImpl.java (added)
+++ felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/UserAdminImpl.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,210 @@
+/**
+ *  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.impl;
+
+import java.util.List;
+
+import org.apache.felix.useradmin.RoleFactory;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+import org.osgi.service.useradmin.UserAdminEvent;
+
+/**
+ * Provides the implementation for {@link UserAdmin}.
+ */
+public class UserAdminImpl implements ServiceFactory, UserAdmin, RoleChangeListener {
+    
+    private final RoleRepository m_roleRepository;
+    private final EventDispatcher m_eventDispatcher;
+
+    private volatile ServiceReference m_serviceRef;
+    
+    /**
+     * Creates a new {@link UserAdminImpl} implementation.
+     * 
+     * @param roleRepository the repository with roles to use for this service;
+     * @param eventDispatcher the event dispatcher to use for this service.
+     * 
+     * @throws IllegalArgumentException in case one of the given parameters was <code>null</code>.
+     */
+    public UserAdminImpl(RoleRepository roleRepository, EventDispatcher eventDispatcher) {
+        if (roleRepository == null) {
+            throw new IllegalArgumentException("RoleRepository cannot be null!");
+        }
+        if (eventDispatcher == null) {
+            throw new IllegalArgumentException("EventDispatcher cannot be null!");
+        }
+
+        m_roleRepository = roleRepository;
+        m_eventDispatcher = eventDispatcher;
+
+        m_roleRepository.addRoleChangeListener(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Role createRole(String name, int type) {
+        if ((type != Role.USER) && (type != Role.GROUP)) {
+            throw new IllegalArgumentException("Invalid type, must by either Role.USER or Role.GROUP!");
+        }
+        if (name == null) {
+            throw new IllegalArgumentException("Invalid name, must be non-null and non-empty!");
+        }
+
+        return m_roleRepository.addRole(RoleFactory.createRole(type, name));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Authorization getAuthorization(User user) {
+        return new AuthorizationImpl(user, m_roleRepository);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Role getRole(String name) {
+        return m_roleRepository.getRoleByName(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Role[] getRoles(String filter) throws InvalidSyntaxException {
+        List roles = m_roleRepository.getRoles(createFilter(filter));
+        if (roles.isEmpty()) {
+            return null;
+        }
+        return (Role[]) roles.toArray(new Role[roles.size()]);
+    }
+    
+    /**
+     * {@inheritDoc}
+     * 
+     * <p>Overridden in order to get hold of our service reference.</p>
+     */
+    public Object getService(Bundle bundle, ServiceRegistration registration) {
+        m_serviceRef = registration.getReference();
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public User getUser(String key, String value) {
+        User result = null;
+        List roles = m_roleRepository.getRoles(key, value);
+        if (roles.size() == 1) {
+            Role foundRole = (Role) roles.get(0);
+            if (foundRole.getType() == Role.USER) {
+                result = (User) foundRole;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void propertyAdded(Role role, Object key, Object value) {
+        m_eventDispatcher.dispatch(createUserAdminEvent(UserAdminEvent.ROLE_CHANGED, role));
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public void propertyChanged(Role role, Object key, Object oldValue, Object newValue) {
+        m_eventDispatcher.dispatch(createUserAdminEvent(UserAdminEvent.ROLE_CHANGED, role));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void propertyRemoved(Role role, Object key) {
+        m_eventDispatcher.dispatch(createUserAdminEvent(UserAdminEvent.ROLE_CHANGED, role));
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public boolean removeRole(String name) {
+        Role role = getRole(name);
+        if (role == null) {
+            return false;
+        }
+
+        return m_roleRepository.removeRole(role);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void roleAdded(Role role) {
+        m_eventDispatcher.dispatch(createUserAdminEvent(UserAdminEvent.ROLE_CREATED, role));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void roleRemoved(Role role) {
+        m_eventDispatcher.dispatch(createUserAdminEvent(UserAdminEvent.ROLE_REMOVED, role));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) {
+        // Nop; we leave the service as-is...
+    }
+    
+    /**
+     * Creates a {@link Filter} instance for the given OSGi/LDAP filter.
+     * 
+     * @param filter the filter to convert to a {@link Filter} instance.
+     * @return a {@link Filter} instance corresponding to the given filter string, never <code>null</code>.
+     * @throws InvalidSyntaxException in case the given filter was invalid.
+     */
+    protected Filter createFilter(String filter) throws InvalidSyntaxException {
+        if (filter == null || "".equals(filter.trim())) {
+            return null;
+        }
+        return FrameworkUtil.createFilter(filter);
+    }
+
+    /**
+     * Creates a new {@link UserAdminEvent} instance for the given type and role.
+     * 
+     * @param type the type of event to create;
+     * @param role the role to create the event for.
+     * @return a new {@link UserAdminEvent} instance, never <code>null</code>.
+     */
+    private UserAdminEvent createUserAdminEvent(int type, Role role) {
+        return new UserAdminEvent(m_serviceRef, type, role);
+    }
+}

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

Added: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/UserAdminListenerList.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/UserAdminListenerList.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/UserAdminListenerList.java (added)
+++ felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/UserAdminListenerList.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,33 @@
+/**
+ *  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.impl;
+
+import org.osgi.service.useradmin.UserAdminListener;
+
+/**
+ * Provides an abstraction for a list of {@link UserAdminListener}s.
+ */
+public interface UserAdminListenerList {
+
+    /**
+     * Returns all current listeners.
+     * 
+     * @return an array of {@link UserAdminListener}s, never <code>null</code>,
+     *         but can be an empty array.
+     */
+    UserAdminListener[] getListeners();
+}

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

Added: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/GroupImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/GroupImpl.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/GroupImpl.java (added)
+++ felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/GroupImpl.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,181 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.felix.useradmin.impl.role;
+
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.UserAdminPermission;
+
+/**
+ * Provides an implementation of {@link Group}. 
+ */
+public class GroupImpl extends UserImpl implements Group {
+
+    private static final String BASIC_MEMBER = "basicMember";
+    private static final String REQUIRED_MEMBER = "requiredMember";
+
+    private final Object m_lock = new Object();
+
+    private final Map m_members;
+    private final Map m_requiredMembers;
+
+    /**
+     * Creates a new {@link GroupImpl} instance of type {@link Role#GROUP}.
+     * 
+     * @param name the name of this group role, cannot be <code>null</code> or empty.
+     */
+    public GroupImpl(String name) {
+        super(Role.GROUP, name);
+        
+        m_members = new HashMap();
+        m_requiredMembers = new HashMap();
+    }
+
+    /**
+     * Creates a new {@link GroupImpl} instance of type {@link Role#GROUP}.
+     * 
+     * @param name the name of this group role, cannot be <code>null</code> or empty.
+     */
+    public GroupImpl(String name, Dictionary properties, Dictionary credentials) {
+        super(Role.GROUP, name, properties, credentials);
+
+        m_members = new HashMap();
+        m_requiredMembers = new HashMap();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean addMember(Role role) {
+        checkPermissions();
+        
+        Object result;
+        synchronized (m_lock) {
+            if (m_members.containsKey(role.getName()) || m_requiredMembers.containsKey(role.getName())) {
+                return false;
+            }
+            result = m_members.put(role.getName(), role);
+        }
+
+        if (result == null) {
+            // Notify our (optional) listener...
+            entryAdded(BASIC_MEMBER, role);
+        }
+        
+        return (result == null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean addRequiredMember(Role role) {
+        checkPermissions();
+
+        Object result;
+        synchronized (m_lock) {
+            if (m_requiredMembers.containsKey(role.getName()) || m_members.containsKey(role.getName())) {
+                return false;
+            }
+            result = m_requiredMembers.put(role.getName(), role);
+        }
+
+        if (result == null) {
+            // Notify our (optional) listener...
+            entryAdded(REQUIRED_MEMBER, role);
+        }
+        
+        return (result == null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Role[] getMembers() {
+        Role[] roles;
+        synchronized (m_lock) {
+            Collection values = m_members.values();
+            roles = (Role[]) values.toArray(new Role[values.size()]);
+        }
+
+        return (roles.length == 0) ? null : roles;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Role[] getRequiredMembers() {
+        Role[] roles;
+        synchronized (m_lock) {
+            Collection values = m_requiredMembers.values();
+            roles = (Role[]) values.toArray(new Role[values.size()]);
+        }
+
+        return (roles.length == 0) ? null : roles;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean removeMember(Role role) {
+        checkPermissions();
+
+        String key = null;
+        Object result = null;
+        
+        synchronized (m_lock) {
+            if (m_requiredMembers.containsKey(role.getName())) {
+                key = REQUIRED_MEMBER;
+                result = m_requiredMembers.remove(role.getName());
+            }
+            else if (m_members.containsKey(role.getName())) {
+                key = BASIC_MEMBER;
+                result = m_members.remove(role.getName());
+            }
+        }
+
+        if (result != null) {
+            // Notify our (optional) listener...
+            entryRemoved(key);
+        }
+        
+        return result != null;
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public String toString() {
+        return "Group(" + getName() + "): R{" + m_requiredMembers + "}, B{" + m_members + "}";
+    }
+
+    /**
+     * Verifies whether the caller has the right permissions to get or change the given key.
+     * 
+     * @throws SecurityException in case the caller has not the right permissions to perform the action.
+     */
+    private void checkPermissions() throws SecurityException {
+        SecurityManager securityManager = System.getSecurityManager();
+        if (securityManager != null) {
+            securityManager.checkPermission(new UserAdminPermission(UserAdminPermission.ADMIN, null));
+        }
+    }
+}

Propchange: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/GroupImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/ObservableDictionary.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/ObservableDictionary.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/ObservableDictionary.java (added)
+++ felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/ObservableDictionary.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,311 @@
+/**
+ *  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.impl.role;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.osgi.service.useradmin.UserAdminPermission;
+
+/**
+ * Provides an observable {@link Dictionary} implementation that emits change 
+ * events for the put and remove operations aside checking for security 
+ * permissions for all accessor methods.
+ */
+class ObservableDictionary extends Dictionary implements Serializable {
+
+    private static final long serialVersionUID = 9223154895541178975L;
+
+    /**
+     * Provides a listener for changes to a {@link ObservableDictionary}.
+     */
+    static interface DictionaryChangeListener {
+
+        /**
+         * Called when a new entry is added.
+         * 
+         * @param key the key of the entry;
+         * @param value the value associated to the key.
+         */
+        void entryAdded(Object key, Object value);
+        
+        /**
+         * Called when an entry is changed.
+         * 
+         * @param key the key of the entry;
+         * @param oldValue the old value associated to the key;
+         * @param newValue the new value associated to the key.
+         */
+        void entryChanged(Object key, Object oldValue, Object newValue);
+        
+        /**
+         * Called when an entry is removed.
+         * 
+         * @param key the key of the entry.
+         */
+        void entryRemoved(Object key);
+    }
+
+    /**
+     * Provides a wrapper to convert an {@link Iterator} to an {@link Enumeration} implementation.
+     */
+    static final class IteratorEnumeration implements Enumeration {
+        
+        private final Iterator m_iterator;
+
+        /**
+         * Creates a new {@link IteratorEnumeration}.
+         * 
+         * @param iterator the {@link Iterator} to convert to a {@link Enumeration}, cannot be <code>null</code>.
+         */
+        public IteratorEnumeration(Iterator iterator) {
+            m_iterator = iterator;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean hasMoreElements() {
+            return m_iterator.hasNext();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Object nextElement() {
+            return m_iterator.next();
+        }
+    }
+    
+    /**
+     * 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 static ConcurrentMap convertToMap(Dictionary dictionary) {
+        ConcurrentMap result = new ConcurrentHashMap();
+        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;
+    }
+    
+    private final ConcurrentMap m_properties;
+    private final String m_getAction;
+    private final String m_changeAction;
+
+    private transient volatile DictionaryChangeListener m_listener;
+
+    /**
+     * Creates a new, empty, {@link ObservableDictionary} instance.
+     */
+    public ObservableDictionary(String getAction, String changeAction) {
+        m_getAction = getAction;
+        m_changeAction = changeAction;
+        m_properties = new ConcurrentHashMap();
+    }
+
+    /**
+     * Creates a new {@link ObservableDictionary} instance with the given dictionary as defaults.
+     * 
+     * @param dictionary the defaults to set for this properties, cannot be <code>null</code>.
+     */
+    public ObservableDictionary(String getAction, String changeAction, Dictionary dictionary) {
+        if (dictionary == null) {
+            throw new IllegalArgumentException("Dictionary cannot be null!");
+        }
+        m_getAction = getAction;
+        m_changeAction = changeAction;
+        m_properties = convertToMap(dictionary);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Enumeration elements() {
+        Collection values = m_properties.values();
+        return new IteratorEnumeration(values.iterator());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (object == null || (getClass() != object.getClass())) {
+            return false;
+        }
+
+        ObservableDictionary other = (ObservableDictionary) object;
+        if (m_properties == null) {
+            if (other.m_properties != null) {
+                return false;
+            }
+        } else if (!m_properties.equals(other.m_properties)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object get(Object key) {
+        if (key == null) {
+            throw new IllegalArgumentException("Key cannot be null!");
+        }
+
+        if (m_getAction != null) {
+            checkPermissions(getAsPermissionKey(key), m_getAction);
+        }
+
+        return m_properties.get(key);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode() {
+        final int prime = 37;
+        int result = 1;
+        result = prime * result + ((m_properties == null) ? 0 : m_properties.hashCode());
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isEmpty() {
+        return m_properties.isEmpty();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Enumeration keys() {
+        Collection keys = m_properties.keySet();
+        return new IteratorEnumeration(keys.iterator());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object put(Object key, Object value) {
+        if (key == null) {
+            throw new IllegalArgumentException("Key cannot be null!");
+        }
+        if (value == null) {
+            throw new IllegalArgumentException("Value cannot be null!");
+        }
+
+        if (m_changeAction != null) {
+            checkPermissions(getAsPermissionKey(key), m_changeAction);
+        }
+
+        Object oldValue = m_properties.put(key, value);
+        
+        final DictionaryChangeListener listener = m_listener;
+        if (listener != null) {
+            if (oldValue == null) {
+                listener.entryAdded(key, value);
+            } else {
+                listener.entryChanged(key, oldValue, value);
+            }
+        }
+
+        return oldValue;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object remove(Object key) {
+        if (key == null) {
+            throw new IllegalArgumentException("Key cannot be null!");
+        }
+
+        if (m_changeAction != null) {
+            checkPermissions(getAsPermissionKey(key), m_changeAction);
+        }
+
+        Object oldValue = m_properties.remove(key);
+        final DictionaryChangeListener listener = m_listener;
+        if (listener != null) {
+            listener.entryRemoved(key);
+        }
+        
+        return oldValue;
+    }
+
+    /**
+     * Sets a new {@link DictionaryChangeListener} to observe changes to this dictionary.
+     * 
+     * @param listener the listener to add, can be <code>null</code> to stop listening for changes.
+     */
+    public void setDictionaryChangeListener(DictionaryChangeListener listener) {
+        m_listener = listener;
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public int size() {
+        return m_properties.size();
+    }
+
+    /**
+     * @param key
+     * @return
+     */
+    protected String getAsPermissionKey(Object key) {
+        String k = UserAdminPermission.ADMIN;
+        if (key instanceof String) {
+            k = (String) key;
+        }
+        return k;
+    }
+
+    /**
+     * Verifies whether the caller has the right permissions to get or change the given key.
+     * 
+     * @param key the name of the property that is to be accessed or changed, cannot be <code>null</code>;
+     * @param action the action name to perform, cannot be <code>null</code>.
+     * @throws SecurityException in case the caller has not the right permissions to perform the action.
+     */
+    private void checkPermissions(String key, String action) throws SecurityException {
+        SecurityManager securityManager = System.getSecurityManager();
+        if (securityManager != null) {
+            securityManager.checkPermission(new UserAdminPermission(key, action));
+        }
+    }
+}

Propchange: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/ObservableDictionary.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/ObservableProperties.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/ObservableProperties.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/ObservableProperties.java (added)
+++ felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/ObservableProperties.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,80 @@
+/**
+ *  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.impl.role;
+
+import java.util.Dictionary;
+
+/**
+ * Provides an stricter variant of the {@link ObservableDictionary} that only 
+ * permits string keys and values of either String or byte[]. 
+ */
+final class ObservableProperties extends ObservableDictionary {
+
+    private static final long serialVersionUID = -2513082903921734796L;
+
+    /**
+     * Creates a new, empty, {@link ObservableProperties} instance.
+     */
+    public ObservableProperties(String getAction, String changeAction) {
+        super(getAction, changeAction);
+    }
+
+    /**
+     * Creates a new {@link ObservableProperties} instance with the given dictionary as defaults.
+     * 
+     * @param dictionary the defaults to set for this properties, cannot be <code>null</code>.
+     */
+    public ObservableProperties(String getAction, String changeAction, Dictionary dictionary) {
+        super(getAction, changeAction, dictionary);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object get(Object key) {
+        if (!(key instanceof String)) {
+            throw new IllegalArgumentException("Key must be of type String!");
+        }
+
+        return super.get(key);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object put(Object key, Object value) {
+        if (!(key instanceof String)) {
+            throw new IllegalArgumentException("Key must be of type String!");
+        }
+        if (!(value instanceof String) && !(value instanceof byte[])) {
+            throw new IllegalArgumentException("Value must be of type String or byte[]!");
+        }
+
+        return super.put(key, value);
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public Object remove(Object key) {
+        if (!(key instanceof String)) {
+            throw new IllegalArgumentException("Key must be of type String!");
+        }
+
+        return super.remove(key);
+    }
+}

Propchange: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/ObservableProperties.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/RoleImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/RoleImpl.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/RoleImpl.java (added)
+++ felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/RoleImpl.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,185 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.felix.useradmin.impl.role;
+
+import java.util.Dictionary;
+
+
+import org.apache.felix.useradmin.impl.RoleChangeListener;
+import org.apache.felix.useradmin.impl.role.ObservableDictionary.DictionaryChangeListener;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.UserAdminPermission;
+
+/**
+ * Provides an implementation of {@link Role}.
+ */
+public class RoleImpl implements Role, DictionaryChangeListener {
+
+    private final ObservableProperties m_properties;
+    private final String m_name;
+    private final int m_type;
+    
+    private volatile RoleChangeListener m_listener;
+
+    /**
+     * Creates a new {@link RoleImpl} instance of type {@link Role#ROLE} and a given name.
+     * 
+     * @param name the name of this role, cannot be <code>null</code> or empty.
+     */
+    public RoleImpl(String name) {
+        this(Role.ROLE, name);
+    }
+
+    /**
+     * Creates a new {@link RoleImpl} instance with a given type and name.
+     * 
+     * @param type the type of this role, should be {@link Role#ROLE}, {@link Role#USER} or {@link Role#GROUP};
+     * @param name the name of this role, cannot be <code>null</code> or empty.
+     */
+    protected RoleImpl(int type, String name) {
+        if (name == null || "".equals(name.trim())) {
+            throw new IllegalArgumentException("Name cannot be null or empty!");
+        }
+        m_type = type;
+        m_name = name;
+        m_properties = new ObservableProperties(null, UserAdminPermission.CHANGE_PROPERTY);
+        m_properties.setDictionaryChangeListener(this);
+    }
+
+    /**
+     * Creates a new {@link RoleImpl} instance with a given type, name and properties.
+     * 
+     * @param type the type of this role, should be {@link Role#ROLE}, {@link Role#USER} or {@link Role#GROUP};
+     * @param name the name of this role, cannot be <code>null</code> or empty;
+     * @param properties the initial properties of this role, cannot be <code>null</code>.
+     */
+    protected RoleImpl(int type, String name, Dictionary properties) {
+        if (name == null || "".equals(name.trim())) {
+            throw new IllegalArgumentException("Name cannot be null or empty!");
+        }
+        m_type = type;
+        m_name = name;
+        m_properties = new ObservableProperties(null, UserAdminPermission.CHANGE_PROPERTY, properties);
+        m_properties.setDictionaryChangeListener(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final void entryAdded(Object key, Object value) {
+        RoleChangeListener listener = m_listener;
+        if (listener != null) {
+            listener.propertyAdded(this, key, value);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final void entryRemoved(Object key) {
+        RoleChangeListener listener = m_listener;
+        if (listener != null) {
+            listener.propertyRemoved(this, key);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public final void entryChanged(Object key, Object oldValue, Object newValue) {
+        RoleChangeListener listener = m_listener;
+        if (listener != null) {
+            listener.propertyChanged(this, key, oldValue, newValue);
+        }
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if ((obj == null) || (getClass() != obj.getClass())) {
+            return false;
+        }
+
+        RoleImpl other = (RoleImpl) obj;
+        if (m_name == null) {
+            if (other.m_name != null) {
+                return false;
+            }
+        } else if (!m_name.equals(other.m_name)) {
+            return false;
+        }
+
+        if (m_type != other.m_type) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getName() {
+        return m_name;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Dictionary getProperties() {
+        return m_properties;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getType() {
+        return m_type;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((m_name == null) ? 0 : m_name.hashCode());
+        result = prime * result + m_type;
+        return result;
+    }
+
+    /**
+     * Sets the {@link RoleChangeListener} for this role implementation.
+     * 
+     * @param listener the listener to set, may be <code>null</code> to stop listening.
+     */
+    public void setRoleChangeListener(RoleChangeListener listener) {
+        m_listener = listener;
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public String toString() {
+        return "Role(" + getName() + ")";
+    }
+}

Propchange: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/RoleImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/UserImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/UserImpl.java?rev=1391437&view=auto
==============================================================================
--- felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/UserImpl.java (added)
+++ felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/UserImpl.java Fri Sep 28 12:58:59 2012
@@ -0,0 +1,119 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.felix.useradmin.impl.role;
+
+import java.util.Arrays;
+import java.util.Dictionary;
+
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdminPermission;
+
+/**
+ * Provides an implementation of {@link User}.
+ */
+public class UserImpl extends RoleImpl implements User {
+
+    private final ObservableProperties m_credentials;
+
+    /**
+     * Creates a new {@link UserImpl} instance with type {@link Role#USER}.
+     * 
+     * @param name the name of this user role, cannot be <code>null</code> or empty.
+     */
+    public UserImpl(String name) {
+        this(Role.USER, name);
+    }
+
+    /**
+     * Creates a new {@link UserImpl} instance with a given type.
+     *
+     * @param type the type of this role;
+     * @param name the name of this role, cannot be <code>null</code> or empty.
+     */
+    protected UserImpl(int type, String name) {
+        super(type, name);
+
+        m_credentials = new ObservableProperties(UserAdminPermission.GET_CREDENTIAL, UserAdminPermission.CHANGE_CREDENTIAL);
+        m_credentials.setDictionaryChangeListener(this);
+    }
+
+    /**
+     * Creates a new {@link UserImpl} instance with type {@link Role#USER}.
+     * 
+     * @param name the name of this user role, cannot be <code>null</code> or empty.
+     */
+    protected UserImpl(int type, String name, Dictionary properties, Dictionary credentials) {
+        super(type, name, properties);
+
+        m_credentials = new ObservableProperties(UserAdminPermission.GET_CREDENTIAL, UserAdminPermission.CHANGE_CREDENTIAL, credentials);
+        m_credentials.setDictionaryChangeListener(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Dictionary getCredentials() {
+        return m_credentials;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasCredential(String key, Object value) {
+        // Will throw a SecurityException if we're not allowed to do this!
+        Object result = m_credentials.get(key);
+
+        // Be a bit more lenient with the various results we can get...
+        if (result instanceof String) {
+            String s1 = (String) result;
+            String s2;
+            if (value instanceof byte[]) {
+                s2 = new String((byte[]) value);
+            } else if (value instanceof String) {
+                s2 = (String) value;
+            } else {
+                // Not a string or a byte-array!
+                return false;
+            }
+            
+            return s1.equals(s2);
+        } else if (result instanceof byte[]) {
+            byte[] b1 = (byte[]) result;
+            byte[] b2;
+            if (value instanceof byte[]) {
+                b2 = (byte[]) value;
+            } else if (value instanceof String) {
+                b2 = ((String) value).getBytes();
+            } else {
+                // Not a string or a byte-array!
+                return false;
+            }
+
+            return Arrays.equals(b1, b2);
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString() {
+        return "User(" + getName() + ")";
+    }
+}

Propchange: felix/trunk/useradmin/useradmin/src/main/java/org/apache/felix/useradmin/impl/role/UserImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native