Author: ivol37 at gmail.com
Date: Fri Dec 17 14:15:20 2010
New Revision: 510

Log:
[AMDATU-226] Moved the UserAdmin REST interface to its own bundle

Added:
   trunk/amdatu-authorization/useradmin-rest/
   trunk/amdatu-authorization/useradmin-rest/pom.xml
   trunk/amdatu-authorization/useradmin-rest/src/
   trunk/amdatu-authorization/useradmin-rest/src/main/
   trunk/amdatu-authorization/useradmin-rest/src/main/java/
   trunk/amdatu-authorization/useradmin-rest/src/main/java/org/
   trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/
   
trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/authorization/
   
trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/authorization/useradmin/
   
trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/authorization/useradmin/rest/
   
trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/authorization/useradmin/rest/osgi/
   
trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/authorization/useradmin/rest/osgi/Activator.java
   
trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/authorization/useradmin/rest/service/
   
trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/authorization/useradmin/rest/service/GroupsResource.java
   
trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/authorization/useradmin/rest/service/ResourceBase.java
   
trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/authorization/useradmin/rest/service/UsersResource.java
   
trunk/amdatu-web/rest-jaxrs/src/main/java/org/amdatu/web/rest/jaxrs/RESTService.java
   
trunk/integration-tests/src/test/java/org/amdatu/test/integration/base/RESTTestBase.java
   
trunk/integration-tests/src/test/java/org/amdatu/test/integration/tests/UserAdminRESTTest.java
Removed:
   
trunk/amdatu-cassandra/cassandra-useradminstore/src/main/java/org/amdatu/cassandra/useradminstore/DummyResourceInterface.java
   
trunk/amdatu-cassandra/cassandra-useradminstore/src/main/java/org/amdatu/cassandra/useradminstore/rest/GroupsResource.java
   
trunk/amdatu-cassandra/cassandra-useradminstore/src/main/java/org/amdatu/cassandra/useradminstore/rest/ResourceBase.java
   
trunk/amdatu-cassandra/cassandra-useradminstore/src/main/java/org/amdatu/cassandra/useradminstore/rest/UsersResource.java
Modified:
   trunk/amdatu-authorization/pom.xml
   
trunk/amdatu-cassandra/cassandra-useradminstore/src/main/java/org/amdatu/cassandra/useradminstore/osgi/Activator.java
   trunk/amdatu-release/pom.xml
   trunk/amdatu-web/rest-jaxrs/pom.xml
   trunk/integration-tests/pom.xml
   
trunk/integration-tests/src/test/java/org/amdatu/test/integration/base/IntegrationTestBase.java
   trunk/src/main/resources/conf/felix-config.properties

Modified: trunk/amdatu-authorization/pom.xml
==============================================================================
--- trunk/amdatu-authorization/pom.xml  (original)
+++ trunk/amdatu-authorization/pom.xml  Fri Dec 17 14:15:20 2010
@@ -48,6 +48,7 @@
   <modules>
     <module>login-gadget</module>
     <module>login-service</module>
+    <module>useradmin-rest</module>
   </modules>
 
 </project>
\ No newline at end of file

Added: trunk/amdatu-authorization/useradmin-rest/pom.xml
==============================================================================
--- (empty file)
+++ trunk/amdatu-authorization/useradmin-rest/pom.xml   Fri Dec 17 14:15:20 2010
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.amdatu</groupId>
+    <artifactId>org.amdatu.authorization</artifactId>
+    <version>0.1.0-SNAPSHOT</version>
+  </parent>
+  <groupId>org.amdatu.authorization.useradmin</groupId>
+  <artifactId>rest</artifactId>
+  <packaging>bundle</packaging>
+  <name>Amdatu Authorization - User Admin REST API</name>
+  <description>Provides a REST API on UserAdmin</description>
+  
+  <dependencies>  
+    <dependency>
+      <groupId>org.amdatu.web</groupId>
+      <artifactId>httpcontext</artifactId>
+      <scope>provided</scope>
+      <type>bundle</type>
+    </dependency>
+    <dependency>
+      <groupId>org.amdatu.web.rest</groupId>
+      <artifactId>jaxrs</artifactId>
+      <version>${platform.version}</version>
+      <scope>provided</scope>
+      <type>bundle</type>
+    </dependency>
+    <dependency>
+      <groupId>org.json</groupId>
+      <artifactId>json</artifactId>
+      <version>20090211</version>
+      <scope>compile</scope>
+    </dependency>       
+  </dependencies>
+  
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <configuration>
+          <instructions>
+            
<Bundle-Activator>org.amdatu.authorization.useradmin.rest.osgi.Activator</Bundle-Activator>
+            <Bundle-SymbolicName> 
org.amdatu.authorization.useradmin.rest</Bundle-SymbolicName>
+            <Embed-Dependency>*;scope=compile</Embed-Dependency>
+          </instructions>
+        </configuration>
+      </plugin>   
+    </plugins>        
+  </build> 
+</project>

Added: 
trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/authorization/useradmin/rest/osgi/Activator.java
==============================================================================
--- (empty file)
+++ 
trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/authorization/useradmin/rest/osgi/Activator.java
 Fri Dec 17 14:15:20 2010
@@ -0,0 +1,53 @@
+/*
+    Copyright (C) 2010 Amdatu.org
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.amdatu.authorization.useradmin.rest.osgi;
+
+import org.amdatu.authorization.useradmin.rest.service.GroupsResource;
+import org.amdatu.authorization.useradmin.rest.service.UsersResource;
+import org.amdatu.web.rest.jaxrs.RESTService;
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * This is the activator for the UserAdmin REST API bundle
+ * 
+ * @author ivol
+ */
+public class Activator extends DependencyActivatorBase {
+
+    public void init(BundleContext context, DependencyManager manager) throws 
Exception {
+        // Create the users resource service and register it as REST service
+        manager.add(createComponent()
+            .setInterface(RESTService.class.getName(), null)
+            .setImplementation(UsersResource.class)
+            
.add(createServiceDependency().setService(LogService.class).setRequired(true))
+            
.add(createServiceDependency().setService(UserAdmin.class).setRequired(true))); 
  
+
+        // Create the groups resource service and register it as REST service
+        manager.add(createComponent()
+            .setInterface(RESTService.class.getName(), null)
+            .setImplementation(GroupsResource.class)
+            
.add(createServiceDependency().setService(LogService.class).setRequired(true))
+            
.add(createServiceDependency().setService(UserAdmin.class).setRequired(true)));
+    }
+
+    public void destroy(BundleContext context, DependencyManager manager) 
throws Exception {
+    }
+}

Added: 
trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/authorization/useradmin/rest/service/GroupsResource.java
==============================================================================
--- (empty file)
+++ 
trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/authorization/useradmin/rest/service/GroupsResource.java
 Fri Dec 17 14:15:20 2010
@@ -0,0 +1,280 @@
+/*
+    Copyright (C) 2010 Amdatu.org
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.amdatu.authorization.useradmin.rest.service;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.CacheControl;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.amdatu.web.rest.jaxrs.RESTService;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+
+/**
+ * REST service for managing Groups based on the UserAdmin OSGi specification 
(see chapter 107 of the Enterprise OSGi
+ * spec version 4.1). This REST service provides the following methods: 
<code><pre>
+ * GET /rest/services/groups/groups -> returns all users
+ * GET /rest/services/groups/groups/{name} -> Returns the group with the 
specified name
+ * PUT /rest/services/groups/groups/{name} -> Creates a group with the 
specified name
+ * PUT /rest/services/groups/groups/{name}/basicmembers/{memberName} -> Adds a 
member to the group
+ * PUT /rest/services/groups/groups/{name}/requiredmembers/{memberName} -> 
Adds a required member to the group 
+ * DELETE /rest/services/groups/groups/{name} -> Deletes the group with the 
specified name
+ * DELETE /rest/services/groups/groups/{name}/members/{memberName} -> Deletes 
a member from group
+ * </pre></code>
+ * @author ivol
+ */
+ at Path("groups")
+public class GroupsResource extends ResourceBase implements RESTService {
+    // Disable HTTP caching in this REST interface
+    private static CacheControl m_cacheControl;
+    static {
+        m_cacheControl = new CacheControl();
+        m_cacheControl.setNoCache(true);
+    }
+    
+    /**
+     * This method can be used to check the availability of the Groups 
management service.
+     * @return The text "UserAdmin Groups management service online"
+     */
+    @GET
+    @Produces({MediaType.TEXT_PLAIN})
+    public String status() {
+        return "UserAdmin Groups management service online";
+    } 
+    
+    /**
+     * Returns all groups that match the specified filter options. This method 
can be invoked by making the following
+     * REST call:<code><pre>
+     * GET 
/rest/services/groups/groups?filter={filter}&sortOrder={sortOrder}&startIndex={startIndex}&maxResults={maxResults}
+     * </pre></code> For available path and query parameters, see below.
+     * @param filter Filter that the name of the group must match. A group 
name is considered to match the specified
+     *            filter if the filter equals the group name or the filter is 
a substring of the group name.
+     * @param sortOrder 'ascending' to return results in ascending order or 
'descending' to return results in descending
+     *            order.
+     * @param startIndex The index of the first result to return (default is 
1).
+     * @param maxResults The maximum amount of results to return (default is 
50).
+     * @return a 400 response with this JSON string containing the results in 
case the call was successful: <code><pre>
+     * {
+     *   "totalCount":1,             => Total amount of available results for 
this query, independent of the values of startIndex and maxResults
+     *   "resultCount":1,            => Amount of results returned, depending 
on startIndex and maxResults
+     *   "maxResults":50             => The maximum amount of results returned 
in the result (if provided as request parameter, this value is the same)
+     *   "startIndex":1,             => Index of the first search result 
returned (if provided as request parameter, this value is the same)
+     *   "endIndex":1,               => Index of the last search result 
returned
+     *   "firstStartIndex":1,        => Index of the first available result 
for this query
+     *   "lastStartIndex":1,         => Last index of available results for 
this query (i.e. if there are 243 results available and maxResults is 50, this 
value equals 201)
+     *   "entry":[                   => Result entries
+     *    {
+     *      "name":"Administrators", => Name of the group
+     *      "properties":{},         => Properties of the group
+     *      "members":{}             => Members of the group
+     *      "requiredMembers":{}     => Required members of the group
+     *    }]
+     * }
+     * </pre></code> a 500 response in case any exception occurred.
+     */
+    @GET
+    @Produces({MediaType.APPLICATION_JSON})
+    public Response getGroups(@QueryParam("filter") final String filter,
+            @QueryParam("sortOrder") final String sortOrder,
+            @DefaultValue("1") @QueryParam("startIndex") final int startIndex,
+            @DefaultValue("50") @QueryParam("maxResults") final int 
maxResults) {
+        return super.getRoles(filter, sortOrder, startIndex, maxResults, 
Role.GROUP);
+    }
+
+    /**
+     * Returns the group with the specified name. This method can be invoked 
by making the following REST call:
+     * <code><pre>
+     * GET /rest/services/groups/groups/{name}
+     * </pre></code> For available path and query parameters, see below.
+     * @param name Name of the group to retrieve
+     * @return a 400 response in case the group was found and returned. It 
returns this JSON string: <code><pre>
+     *   {
+     *     "name":"Administrators", => Name of the group
+     *     "properties":{},         => Properties of the group
+     *     "members":{},            => Member of the group
+     *     "requiredMembers":{}     => Required members of the group
+     *   }
+     * </pre></code>a 404 response if the requested group does not exist.<br/>
+     *         a 500 response in case any exception occurred.
+     */
+    @GET
+    @Path("{name}")
+    @Produces({MediaType.APPLICATION_JSON})
+    public Response getGroup(@PathParam("name") final String name) {
+        return super.getRole(name, Role.GROUP);
+    }
+
+    /**
+     * Creates the group with the specified name. This method can be invoked 
by making the following REST call:
+     * <code><pre>
+     * PUT /rest/services/groups/groups/{name}
+     * </pre></code> For available path and query parameters, see below.
+     * @param name Name of the group to create
+     * @return a 400 response in case the group was created.<br/>
+     *         a 304 response in case for whatever reason the group could not 
be created (i.e. a group or user with the
+     *         specified name already exists).
+     */
+    @PUT
+    @Path("{name}")
+    @Produces({MediaType.APPLICATION_JSON})
+    public Response createGroup(@PathParam("name") final String name) {
+        return super.createRole(name, Role.GROUP);
+    }
+    
+    /**
+     * Sets a credential for the group with the specified name. This method 
can be invoked by making the following REST
+     * call: <code><pre>
+     * PUT /rest/services/groups/groups/{name}/credentials/{key}
+     * </pre></code> For available path and query parameters, see below.
+     * @param name Name of the group to set the credential for
+     * @param key The key of the credential to set
+     * @param value The value of the credential to set  (this should be a 
posted form field named 'value')
+     * @return a 400 response in case the credential was updated.<br/>
+     *         a 404 response in case the group with the specified name does 
not exist.
+     */
+    @PUT
+    @Path("{name}/credentials/{key}")
+    @Produces({MediaType.APPLICATION_JSON})
+    public Response setCredential(@PathParam("name") final String name,
+            @PathParam("key") final String key, @QueryParam("value") final 
String value) {
+        return setCredential(name, key, value, Role.GROUP);
+    }  
+    
+    /**
+     * Sets a property for the group with the specified name. This method can 
be invoked by making the following REST
+     * call: <code><pre>
+     * PUT /rest/services/groups/groups/{name}/properties/{key}
+     * </pre></code> For available path and query parameters, see below.
+     * @param name Name of the group to set the property for
+     * @param key The key of the property to set
+     * @param value The value of the property to set (this should be a posted 
form field named 'value')
+     * @return a 400 response in case the property was updated.<br/>
+     *         a 404 response in case the group with the specified name does 
not exist.
+     */
+    @PUT
+    @Path("{name}/properties/{key}")
+    @Produces({MediaType.APPLICATION_JSON})
+    public Response setProperty(@PathParam("name") final String name,
+            @PathParam("key") final String key, @QueryParam("value") final 
String value) {
+        return setProperty(name, key, value, Role.USER);
+    }    
+
+    /**
+     * Adds a basic member to the group with the specified name. This method 
can be invoked by making the following REST
+     * call: <code><pre>
+     * PUT /rest/services/groups/groups/{name}/basicmembers/{memberName}
+     * </pre></code> For available path and query parameters, see below.
+     * @param name Name of the group to add the member to
+     * @param memberName Name of the basic member (User or Group) to add
+     * @return a 400 response in case the group was created.<br/>
+     *         a 404 response in case the group or member with the specified 
name does not exist.</br/> a 304 response
+     *         in case the basic member was already assigned to this group.
+     */
+    @PUT
+    @Path("{name}/basicmembers/{memberName}")
+    @Produces({MediaType.APPLICATION_JSON})
+    public Response addBasicMember(@PathParam("name") final String name,
+            @PathParam("memberName") final String memberName) {
+        return addMember(name, memberName, false);
+    }
+
+    /**
+     * Adds a required member to the group with the specified name. This 
method can be invoked by making the following
+     * REST call: <code><pre>
+     * PUT /rest/services/groups/groups/{name}/requiredmembers/{memberName}
+     * </pre></code> For available path and query parameters, see below.
+     * @param name Name of the group to add the member to
+     * @param memberName Name of the required member (User or Group) to add
+     * @return a 400 response in case the group was created.<br/>
+     *         a 404 response in case the group or required member with the 
specified name does not exist.</br/> a 304
+     *         response in case the required member was already assigned to 
this group.
+     */
+    @PUT
+    @Path("{name}/requiredmembers/{memberName}")
+    @Produces({MediaType.APPLICATION_JSON})
+    public Response addRequiredMember(@PathParam("name") final String name,
+            @PathParam("memberName") final String memberName) {
+        return addMember(name, memberName, true);
+    }
+
+    /**
+     * Removes the group with the specified name. This method can be invoked 
by making the following REST call:
+     * <code><pre>
+     * DELETE /rest/services/groups/groups/{name}
+     * </pre></code> For available path and query parameters, see below.
+     * @param name Name of the group to delete
+     * @return a 400 response in case the group was deleted.<br/>
+     *         a 404 response in case a group with the specified name does not 
exist.
+     */
+    @DELETE
+    @Path("{name}")
+    @Produces({MediaType.APPLICATION_JSON})
+    public Response removeGroup(@PathParam("name") final String name) {
+        return super.removeRole(name, Role.GROUP);
+    }
+
+    /**
+     * Removes the member (basic or required) from the group with the 
specified names. This method can be invoked by
+     * making the following REST call: <code><pre>
+     * DELETE /rest/services/groups/groups/{name}/members/{memberName}
+     * </pre></code> For available path and query parameters, see below.
+     * @param name Name of the group to remove the member from
+     * @param memberName Name of the member (User or Group) to remove
+     * @return a 400 response in case the member was removed.<br/>
+     *         a 404 response in case the group or member with the specified 
name does not exist.
+     */
+    @DELETE
+    @Path("{name}/members/{memberName}")
+    @Produces({MediaType.APPLICATION_JSON})
+    public Response removeMember(@PathParam("name") final String name, 
@PathParam("memberName") final String memberName) {
+        Role role = m_userAdmin.getRole(name);
+        Role member = m_userAdmin.getRole(memberName);
+        if (role != null && role.getType() == Role.GROUP && member != null) {
+            if (((Group) role).removeMember(member)) {
+                return buildOK();
+            } else {
+                return buildNotModified("Member already removed");
+            }
+        } else {
+            return buildNotFound();
+        }
+    }
+
+    private Response addMember(String name, String memberName, boolean 
required) {
+        Role role = m_userAdmin.getRole(name);
+        Role member = m_userAdmin.getRole(memberName);
+        if (role != null && role.getType() == Role.GROUP && member != null) {
+            if (!required && ((Group) role).addMember(member)) {
+                return buildOK();
+            } else if (required && ((Group) role).addRequiredMember(member)) {
+                return buildOK();
+            } else {
+                return buildNotModified("Member already added");
+            }
+        } else {
+            return buildNotFound();
+        }
+    }
+}

Added: 
trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/authorization/useradmin/rest/service/ResourceBase.java
==============================================================================
--- (empty file)
+++ 
trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/authorization/useradmin/rest/service/ResourceBase.java
   Fri Dec 17 14:15:20 2010
@@ -0,0 +1,236 @@
+/*
+    Copyright (C) 2010 Amdatu.org
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.amdatu.authorization.useradmin.rest.service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+import javax.ws.rs.core.CacheControl;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.log.LogService;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+abstract class ResourceBase {
+    // Service dependencies injected by the depedency manager
+    protected volatile LogService m_logService;
+    protected volatile UserAdmin m_userAdmin;
+    
+    // Disable HTTP caching in this REST interface
+    private static CacheControl m_cacheControl;
+    static {
+        m_cacheControl = new CacheControl();
+        m_cacheControl.setNoCache(true);
+    }
+
+    /**
+     * Returns the roles (users or groups) for the specified filter options.
+     * @param filter the filter to pass to UserAdmin
+     * @param sortOrder The sort order ('ascending' or 'descending')
+     * @param startIndex The startindex
+     * @param maxResults Maximum amount of results to return on a single REST 
call
+     * @param roleType The type of role to retrieve
+     * @return The response
+     */
+    protected Response getRoles(String filter, String sortOrder, int 
startIndex, int maxResults, int roleType) {
+        try {
+            if (startIndex < 0) {
+                startIndex = 1;
+            }
+            if (maxResults < 0) {
+                maxResults = 50;
+            }
+
+            // First retrieve all roles with the specified filter
+            Role[] roles = m_userAdmin.getRoles(null);
+            final boolean descending = "desc".equalsIgnoreCase(sortOrder) || 
"descending".equalsIgnoreCase(sortOrder);
+
+            // Now filter out the user or groups (we should only return roles 
of type roleType)
+            List<Role> filteredRoles = new ArrayList<Role>();
+            if (roles != null) {
+                for (Role role : roles) {
+                    if (role.getType() == roleType) {
+                        if (filter == null || role.getName().matches(".*" + 
filter + ".*")) {
+                            filteredRoles.add(role);
+                        }
+                    }
+                }
+            }
+
+            // Now filter from startIndex - endIndex
+            List<Role> roleRange = new ArrayList<Role>();
+            for (int i = (startIndex - 1); i < (startIndex + maxResults - 1) 
&& i < filteredRoles.size(); i++) {
+                roleRange.add(filteredRoles.get(i));
+            }
+
+            // Finally sort it
+            Comparator<Role> userComparator = new Comparator<Role>() {
+                public int compare(Role r1, Role r2) {
+                    if (descending) {
+                        return -r1.getName().compareTo(r2.getName());
+                    } else {
+                        return r1.getName().compareTo(r2.getName());
+                    }
+                }
+            };
+            Arrays.sort(roleRange.toArray(new Role[roleRange.size()]), 
userComparator);
+
+            JSONObject jsonObject = new JSONObject();
+            for (Role role : roleRange) {
+                JSONObject jsonUser = getJSON(role);
+                jsonObject.append("entry", jsonUser);
+            }
+
+            jsonObject.put("startIndex", roleRange.size() > 0 ? Math.max(1, 
startIndex) : 0);
+            jsonObject.put("endIndex", (startIndex + roleRange.size() - 1));
+            jsonObject.put("firstStartIndex", roleRange.size() > 0 ? 1 : 0);
+            if (filteredRoles.size() % maxResults == 0) {
+                jsonObject.put("lastStartIndex", Math.max(0, 
filteredRoles.size() - maxResults + 1));
+            } else {
+                jsonObject.put("lastStartIndex", 1 + filteredRoles.size() - 
(filteredRoles.size() % maxResults));
+            }
+            jsonObject.put("maxResults", maxResults);
+            jsonObject.put("resultCount", roleRange.size());
+            jsonObject.put("totalCount", filteredRoles.size());
+
+            return buildOK(jsonObject);
+        } catch (InvalidSyntaxException e) {
+            m_logService.log(LogService.LOG_ERROR, "Unable to retrieve roles 
for filter '" + filter + "', startIndex '"
+                    + startIndex + "' maxResults '" + maxResults + "'", e);
+        } catch (JSONException e) {
+            m_logService.log(LogService.LOG_ERROR, "Unable to retrieve roles 
for filter '" + filter + "', startIndex '"
+                    + startIndex + "' maxResults '" + maxResults + "'", e);
+        }
+        return buildServerError();
+    }
+
+    protected Response getRole(String name, int roleType) {
+        try {
+            Role role = m_userAdmin.getRole(name);
+            if (role != null && role.getType() == roleType) {
+                JSONObject jsonRole = getJSON(role);
+                return buildOK(jsonRole);
+            } else {
+                return buildNotFound();
+            }
+        } catch (JSONException e) {
+            m_logService.log(LogService.LOG_ERROR, "Unable to retrieve role 
named '" + name + "'", e);
+        }
+        return buildServerError();
+    }
+
+    protected Response createRole(String name, int roleType) {
+        Role role = m_userAdmin.createRole(name, roleType);
+        if (role != null) {
+            return buildOK();
+        } else {
+            return buildNotModified("Role could not be created");
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    protected Response setCredential(String name, String key, String value, 
int roleType) {
+        Role role = m_userAdmin.getRole(name);
+        if (role != null && role.getType() == roleType) {
+            ((User) role).getCredentials().put(key, value);
+            return buildOK();
+        } else {
+            return buildNotFound();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    protected Response setProperty(String name, String key, String value, int 
roleType) {
+        Role role = m_userAdmin.getRole(name);
+        if (role != null && role.getType() == roleType) {
+            role.getProperties().put(key, value);
+            return buildOK();
+        } else {
+            return buildNotFound();
+        }
+    }
+
+    protected Response removeRole(String name, int roleType) {
+        Role role = m_userAdmin.getRole(name);
+        if (role != null && role.getType() == roleType) {
+            if (m_userAdmin.removeRole(name)) {
+                return buildOK();
+            } else {
+                return buildNotModified("Role to remove not found");
+            }
+        } else {
+            return buildNotModified("Role to remove not found");
+        }
+    }
+
+    private JSONObject getJSON(Role role) throws JSONException {
+        JSONObject jsonRole = new JSONObject();
+        jsonRole.put("name", role.getName());
+        jsonRole.put("properties", role.getProperties());
+        if (role.getType() == Role.GROUP) {
+            Role[] members = ((Group) role).getMembers();
+            JSONObject jsonMembers = new JSONObject();
+            if (members != null) {
+                for (Role member : members) {
+                    JSONObject jsonMember = getJSON(member);
+                    jsonMember.append("member", jsonMember);
+                }
+            }
+            jsonRole.put("members", jsonMembers);
+
+            Role[] reqMembers = ((Group) role).getRequiredMembers();
+            JSONObject jsonReqMembers = new JSONObject();
+            if (reqMembers != null) {
+                for (Role member : reqMembers) {
+                    JSONObject jsonMember = getJSON(member);
+                    jsonMember.append("member", jsonMember);
+                }
+            }
+            jsonRole.put("requiredMembers", jsonReqMembers);
+        }
+        return jsonRole;
+    }
+    
+    protected Response buildOK() {
+        return Response.ok().cacheControl(m_cacheControl).build();
+    }
+    
+    protected Response buildOK(JSONObject jsonObject) {
+        return Response.ok(jsonObject.toString(), 
MediaType.APPLICATION_JSON_TYPE).cacheControl(m_cacheControl).build();
+    }
+    
+    protected Response buildNotFound() {
+        return 
Response.status(Response.Status.NOT_FOUND).cacheControl(m_cacheControl).build();
+    }
+    
+    protected Response buildNotModified(String msg) {
+        return Response.notModified(msg).cacheControl(m_cacheControl).build();
+    }
+    
+    protected Response buildServerError() {
+        return Response.serverError().cacheControl(m_cacheControl).build();
+    }
+}

Added: 
trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/authorization/useradmin/rest/service/UsersResource.java
==============================================================================
--- (empty file)
+++ 
trunk/amdatu-authorization/useradmin-rest/src/main/java/org/amdatu/authorization/useradmin/rest/service/UsersResource.java
  Fri Dec 17 14:15:20 2010
@@ -0,0 +1,175 @@
+/*
+    Copyright (C) 2010 Amdatu.org
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.amdatu.authorization.useradmin.rest.service;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.amdatu.web.rest.jaxrs.RESTService;
+import org.osgi.service.useradmin.Role;
+
+/**
+ * REST service for managing Users based on the UserAdmin OSGi specification 
(see chapter 107 of the Enterprise OSGi
+ * spec version 4.1). This REST service provides the following methods: 
<code><pre>
+ * GET /rest/services/users/users -> returns all users
+ * GET /rest/services/groups/groups/{name} -> Returns the group with the 
specified name
+ * PUT /rest/services/groups/groups/{name} -> Creates a group with the 
specified name
+ * DELETE /rest/services/groups/groups/{name} -> Deletes the group with the 
specified name
+ * </pre></code>
+ * @author ivol
+ */
+ at Path("users")
+public class UsersResource extends ResourceBase implements RESTService  {    
+    /**
+     * Returns all users that match the specified filter options. This method 
can be invoked by making the following
+     * REST call:<code><pre>
+     * GET 
/rest/services/users/users?filter={filter}&sortOrder={sortOrder}&startIndex={startIndex}&maxResults={maxResults}
+     * </pre></code> For available path and query parameters, see below.
+     * @param filter Filter that the name of the user must match. A user name 
is considered to match the specified
+     *            filter if the filter equals the user name or the filter is a 
substring of the user name.
+     * @param sortOrder 'ascending' to return results in ascending order or 
'descending' to return results in descending
+     *            order.
+     * @param startIndex The index of the first result to return (default is 
1).
+     * @param maxResults The maximum amount of results to return (default is 
50).
+     * @return a 400 response with this JSON string containing the results in 
case the call was successful: <code><pre>
+     * {
+     *   "totalCount":1,             => Total amount of available results for 
this query, independent of the values of startIndex and maxResults
+     *   "resultCount":1,            => Amount of results returned, depending 
on startIndex and maxResults
+     *   "maxResults":50             => The maximum amount of results returned 
in the result (if provided as request parameter, this value is the same)
+     *   "startIndex":1,             => Index of the first search result 
returned (if provided as request parameter, this value is the same)
+     *   "endIndex":1,               => Index of the last search result 
returned
+     *   "firstStartIndex":1,        => Index of the first available result 
for this query
+     *   "lastStartIndex":1,         => Last index of available results for 
this query (i.e. if there are 243 results available and maxResults is 50, this 
value equals 201)
+     *   "entry":[                   => Result entries
+     *    {
+     *      "name":"Administrator",  => Name of the user
+     *      "properties":{}          => Properties of the user
+     *    }]
+     * }
+     * </pre></code> a 500 response in case any exception occurred.
+     */    
+    @GET
+    @Produces({MediaType.APPLICATION_JSON})
+    public Response getUsers(
+            @QueryParam("filter") final String filter,
+            @QueryParam("sortOrder") final String sortOrder,
+            @DefaultValue("1") @QueryParam("startIndex") final int startIndex,
+            @DefaultValue("50") @QueryParam("maxResults") final int 
maxResults) {
+        return super.getRoles(filter, sortOrder, startIndex, maxResults, 
Role.USER);
+    }
+    
+    /**
+     * Returns the user with the specified name. This method can be invoked by 
making the following REST call:
+     * <code><pre>
+     * GET /rest/services/users/users/{name}
+     * </pre></code> For available path and query parameters, see below.
+     * @param name Name of the user to retrieve
+     * @return a 400 response in case the user was found and returned. It 
returns this JSON string: <code><pre>
+     *   {
+     *     "name":"Administrator",  => Name of the user
+     *     "properties":{}          => Properties of the user
+     *   }
+     * </pre></code>a 404 response if the requested user does not exist.<br/>
+     *         a 500 response in case any exception occurred.
+     */
+    @GET
+    @Path("{name}")
+    @Produces({MediaType.APPLICATION_JSON})
+    public Response getUser(@PathParam("name") final String name) {
+        return super.getRole(name, Role.USER);
+    }
+    
+    /**
+     * Creates the user with the specified name. This method can be invoked by 
making the following REST call:
+     * <code><pre>
+     * PUT /rest/services/users/users/{name}
+     * </pre></code> For available path and query parameters, see below.
+     * @param name Name of the user to create
+     * @return a 400 response in case the user was created.<br/>
+     *         a 304 response in case for whatever reason the user could not 
be created (i.e. a group or user with the
+     *         specified name already exists).
+     */
+    @PUT
+    @Path("{name}")
+    @Produces({MediaType.APPLICATION_JSON})
+    public Response createUser(@PathParam("name") final String name) {
+        return super.createRole(name, Role.USER);
+    }
+    
+    /**
+     * Sets a credential for the user with the specified name. This method can 
be invoked by making the following REST
+     * call: <code><pre>
+     * PUT /rest/services/users/users/{name}/credentials/{key}
+     * </pre></code> For available path and query parameters, see below.
+     * @param name Name of the user to set the credential for
+     * @param key The key of the credential to set
+     * @param value The value of the credential to set (this should be a 
posted form field named 'value')
+     * @return a 400 response in case the credential was updated.<br/>
+     *         a 404 response in case the user with the specified name does 
not exist.
+     */
+    @PUT
+    @Path("{name}/credentials/{key}")
+    @Produces({MediaType.APPLICATION_JSON})
+    public Response setCredential(@PathParam("name") final String name,
+            @PathParam("key") final String key, @FormParam("value") final 
String value) {
+        return setCredential(name, key, value, Role.USER);
+    }  
+    
+    /**
+     * Sets a property for the user with the specified name. This method can 
be invoked by making the following REST
+     * call: <code><pre>
+     * PUT /rest/services/users/users/{name}/properties/{key}
+     * </pre></code> For available path and query parameters, see below.
+     * @param name Name of the user to set the property for
+     * @param key The key of the property to set
+     * @param value The value of the property to set (this should be a posted 
form field named 'value')
+     * @return a 400 response in case the property was updated.<br/>
+     *         a 404 response in case the user with the specified name does 
not exist.
+     */
+    @PUT
+    @Path("{name}/properties/{key}")
+    @Produces({MediaType.APPLICATION_JSON})
+    public Response setProperty(@PathParam("name") final String name,
+            @PathParam("key") final String key, @FormParam("value") final 
String value) {
+        return setProperty(name, key, value, Role.USER);
+    }    
+        
+    /**
+     * Removes the user with the specified name. This method can be invoked by 
making the following REST call:
+     * <code><pre>
+     * DELETE /rest/services/users/users/{name}
+     * </pre></code> For available path and query parameters, see below.
+     * @param name Name of the user to delete
+     * @return a 400 response in case the user was deleted.<br/>
+     *         a 404 response in case a user with the specified name does not 
exist.
+     */
+    @DELETE
+    @Path("{name}")
+    @Produces({MediaType.APPLICATION_JSON})
+    public Response removeUser(@PathParam("name") final String name) {
+        return super.removeRole(name, Role.USER);
+    }
+}

Modified: 
trunk/amdatu-cassandra/cassandra-useradminstore/src/main/java/org/amdatu/cassandra/useradminstore/osgi/Activator.java
==============================================================================
--- 
trunk/amdatu-cassandra/cassandra-useradminstore/src/main/java/org/amdatu/cassandra/useradminstore/osgi/Activator.java
       (original)
+++ 
trunk/amdatu-cassandra/cassandra-useradminstore/src/main/java/org/amdatu/cassandra/useradminstore/osgi/Activator.java
       Fri Dec 17 14:15:20 2010
@@ -22,10 +22,7 @@
 import org.amdatu.cassandra.listener.ColumnFamilyAvailable;
 import org.amdatu.cassandra.listener.ColumnFamilyProvider;
 import org.amdatu.cassandra.persistencemanager.CassandraPersistenceManager;
-import org.amdatu.cassandra.useradminstore.DummyResourceInterface;
-import org.amdatu.cassandra.useradminstore.rest.GroupsResource;
 import 
org.amdatu.cassandra.useradminstore.rest.HttpContextRegistrationServiceImpl;
-import org.amdatu.cassandra.useradminstore.rest.UsersResource;
 import org.amdatu.cassandra.useradminstore.service.CassandraStorageProvider;
 import org.amdatu.cassandra.useradminstore.service.RoleColumnFamilyProvider;
 import org.amdatu.web.httpcontext.HttpContextServiceFactory;
@@ -37,7 +34,6 @@
 import org.osgi.framework.BundleContext;
 import org.osgi.service.http.HttpService;
 import org.osgi.service.log.LogService;
-import org.osgi.service.useradmin.UserAdmin;
 
 /**
  * This is the bundle activator for the UserAdmin bundle.
@@ -75,20 +71,6 @@
                 
.add(createServiceDependency().setService(ColumnFamilyAvailable.class, 
roleFilter).setRequired(true))
                 
.add(createServiceDependency().setService(CassandraDaemonService.class).setRequired(true)));
         
-        // Create the users resource and register it as REST service
-        manager.add(createComponent()
-                .setInterface(DummyResourceInterface.class.getName(), null)
-                .setImplementation(UsersResource.class)
-                
.add(createServiceDependency().setService(LogService.class).setRequired(true))
-                
.add(createServiceDependency().setService(UserAdmin.class).setRequired(true))); 
  
-        
-        // Create the users resource and register it as REST service
-        manager.add(createComponent()
-                .setImplementation(GroupsResource.class)
-                .setInterface(DummyResourceInterface.class.getName(), null)
-                
.add(createServiceDependency().setService(LogService.class).setRequired(true))
-                
.add(createServiceDependency().setService(UserAdmin.class).setRequired(true)));
-
         // Create and register the http context registration service
         manager.add(createComponent()
                 .setInterface(ResourceProvider.class.getName(), null)

Modified: trunk/amdatu-release/pom.xml
==============================================================================
--- trunk/amdatu-release/pom.xml        (original)
+++ trunk/amdatu-release/pom.xml        Fri Dec 17 14:15:20 2010
@@ -70,6 +70,13 @@
       <scope>compile</scope>
       <type>bundle</type>
     </dependency>
+    <dependency>
+      <groupId>org.amdatu.authorization.useradmin</groupId>
+      <artifactId>rest</artifactId>
+      <version>${platform.version}</version>
+      <scope>compile</scope>
+      <type>bundle</type>
+    </dependency>
 
     <!-- Core bundles -->
     <dependency>

Modified: trunk/amdatu-web/rest-jaxrs/pom.xml
==============================================================================
--- trunk/amdatu-web/rest-jaxrs/pom.xml (original)
+++ trunk/amdatu-web/rest-jaxrs/pom.xml Fri Dec 17 14:15:20 2010
@@ -43,6 +43,7 @@
               javax.ws.rs.*;version=${jsr311-api.version},
               javax.annotation.security
             </_exportcontents>            
+            <Export-Package>org.amdatu.web.rest.jaxrs</Export-Package>
           </instructions>
         </configuration>
       </plugin>

Added: 
trunk/amdatu-web/rest-jaxrs/src/main/java/org/amdatu/web/rest/jaxrs/RESTService.java
==============================================================================
--- (empty file)
+++ 
trunk/amdatu-web/rest-jaxrs/src/main/java/org/amdatu/web/rest/jaxrs/RESTService.java
        Fri Dec 17 14:15:20 2010
@@ -0,0 +1,30 @@
+/*
+    Copyright (C) 2010 Amdatu.org
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.amdatu.web.rest.jaxrs;
+
+/**
+ * Interface implemented by REST services. In the current Wink implementation 
each REST service must
+ * implement an interface to be able to read the JAX-RS annotations from it. 
Note that it really
+ * doesn't matter what interface is implemented by this service, any interface 
will do. As a result some
+ * 'dummy' interfaces were created and implemented by the REST service. For 
now, this interface can
+ * be used instead.
+ * See http://jira.amdatu.org/jira/browse/AMDATU-221
+ * 
+ * @author ivol
+ */
+public interface RESTService {
+}

Modified: trunk/integration-tests/pom.xml
==============================================================================
--- trunk/integration-tests/pom.xml     (original)
+++ trunk/integration-tests/pom.xml     Fri Dec 17 14:15:20 2010
@@ -183,6 +183,13 @@
       <type>bundle</type>
     </dependency>
     <dependency>
+      <groupId>org.amdatu.authorization.useradmin</groupId>
+      <artifactId>rest</artifactId>
+      <version>${platform.version}</version>
+      <scope>test</scope>
+      <type>bundle</type>
+    </dependency>    
+    <dependency>
       <groupId>org.amdatu.authentication.oauth</groupId>
       <artifactId>api</artifactId>    
       <version>${platform.version}</version>

Modified: 
trunk/integration-tests/src/test/java/org/amdatu/test/integration/base/IntegrationTestBase.java
==============================================================================
--- 
trunk/integration-tests/src/test/java/org/amdatu/test/integration/base/IntegrationTestBase.java
     (original)
+++ 
trunk/integration-tests/src/test/java/org/amdatu/test/integration/base/IntegrationTestBase.java
     Fri Dec 17 14:15:20 2010
@@ -109,16 +109,25 @@
                     // And finally deploy ourselves
                     bundle(integrationTestJarFile().toURI().toString())
                 ));
-        if (provisionBundles() == null) {
+        
+        Option[] addOptions = getProvisionedBundles();
+        if (addOptions == null || addOptions.length == 0) {
             return baseOptions;
         }
-        Option[] options = new Option[baseOptions.length + 1];
+       
+        Option[] options = new Option[baseOptions.length + addOptions.length];
         for (int i = 0; i < baseOptions.length; i++) {
             options[i] = baseOptions[i];
         }
-        options[baseOptions.length] = provisionBundles();
+        for (int i = baseOptions.length; i < baseOptions.length + 
addOptions.length; i++) {
+            options[i] = addOptions[i-baseOptions.length];
+        }
         return options;
     }
+    
+    protected Option[] getProvisionedBundles() {
+        return new Option[]{provisionBundles()};
+    }
 
     /**
      * Return the list of bundles here that are required to run the 
integration test.
@@ -302,6 +311,10 @@
         return 
mavenBundle().groupId("org.amdatu.cassandra").artifactId("useradminstore").versionAsInProject();
     }
 
+    protected static MavenArtifactProvisionOption amdatuUserAdminREST() {
+        return 
mavenBundle().groupId("org.amdatu.authorization.useradmin").artifactId("rest").versionAsInProject();
+    }
+
     protected static MavenArtifactProvisionOption amdatuUserAdminFSStore() {
         return 
mavenBundle().groupId("org.amdatu.core").artifactId("useradminstore-fs").versionAsInProject();
     }

Added: 
trunk/integration-tests/src/test/java/org/amdatu/test/integration/base/RESTTestBase.java
==============================================================================
--- (empty file)
+++ 
trunk/integration-tests/src/test/java/org/amdatu/test/integration/base/RESTTestBase.java
    Fri Dec 17 14:15:20 2010
@@ -0,0 +1,132 @@
+/*
+ Copyright (C) 2010 Amdatu.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.amdatu.test.integration.base;
+
+import static junit.framework.Assert.assertTrue;
+import static org.ops4j.pax.exam.CoreOptions.provision;
+
+import java.io.IOException;
+
+import org.amdatu.web.httpcontext.HttpContextServiceFactory;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.methods.DeleteMethod;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.httpclient.methods.PutMethod;
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.junit.Before;
+import org.ops4j.pax.exam.Inject;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.Configuration;
+import org.osgi.framework.BundleException;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.log.LogService;
+
+/**
+ * This is the base class for REST tests.
+ * @author ivol
+ */
+public abstract class RESTTestBase extends IntegrationTestBase {
+
+    private String m_baseUrl = null;
+
+    @Inject
+    private volatile ConfigurationAdmin m_configAdmin;
+    protected volatile LogService m_logService;
+
+    @Configuration
+    public Option[] configure() {
+        return super.configure();
+    }
+    
+    @Override
+    protected Option[] getProvisionedBundles() {
+        return new Option[]{
+            provision(
+                felixHttpServiceJetty(),
+                amdatuHttpContext(),
+                amdatuJaxRs(),
+                amdatuWink(),
+                slingCommons(),
+                slingMime(),
+                commonsHttpClient(),
+                commonsLogging(),
+                commonsCodec()
+               )
+            , provisionBundles()};
+    }
+
+    public Component getTestComponent(DependencyManager manager) {
+        return manager.createComponent().setImplementation(this)
+            
.add(manager.createServiceDependency().setService(ConfigurationAdmin.class).setRequired(true))
+            
.add(manager.createServiceDependency().setService(HttpService.class).setRequired(true))
+            
.add(manager.createServiceDependency().setService(HttpContextServiceFactory.class).setRequired(true));
+    }
+   
+    @Before 
+    public void initConfig() throws IOException, BundleException {
+        m_configAdmin = getService(ConfigurationAdmin.class);
+        m_logService = getService(LogService.class);
+
+        // Add cassandra and templates configs
+        ConfigProvider configProvider = new ConfigProvider();
+        configProvider.addFelixHttpServiceConfig(m_configAdmin);
+        addConfig(configProvider, m_configAdmin);
+        m_logService.log(LogService.LOG_DEBUG, "HttpService config set to " + 
ConfigProvider.HOSTNAME + ":" + ConfigProvider.PORTNR);
+    }
+
+    protected abstract void addConfig(ConfigProvider configProvider, 
ConfigurationAdmin configAdmin) throws IOException;
+    
+    protected String getBaseUrl() throws Exception {
+        if (m_baseUrl == null) {
+            m_baseUrl = "http://"; + ConfigProvider.HOSTNAME + ":" + 
ConfigProvider.PORTNR + "/rest/services";
+        } 
+        return m_baseUrl;
+    }
+    
+    protected String invokeRestApi(String urlPostfix, String httpMethod, int 
expectedStatus) throws Exception {
+        String url = getBaseUrl() + urlPostfix;
+        HttpClient httpClient = new HttpClient();
+        m_logService.log(LogService.LOG_DEBUG, "Invoking REST API '" + url + 
"'");
+        HttpMethod method = null;
+        if (httpMethod.equals(javax.ws.rs.HttpMethod.GET)) {
+            method = new GetMethod(url);
+        } else if (httpMethod.equals(javax.ws.rs.HttpMethod.PUT)) {
+            method = new PutMethod(url);
+        } else if (httpMethod.equals(javax.ws.rs.HttpMethod.DELETE)) {
+            method = new DeleteMethod(url);
+        } else if (httpMethod.equals(javax.ws.rs.HttpMethod.POST)) {
+            method = new PostMethod(url);
+        }
+        try {
+            // Execute the method, this should return a 200
+            int statusCode = httpClient.executeMethod(method);
+            assertTrue("HTTP '" + httpMethod + "' to '" + url + "' returned " 
+ statusCode, statusCode == expectedStatus);
+
+            // Read the response body and return it.
+            return new String(method.getResponseBody(), "UTF-8");
+        }
+        finally {
+            // Release the connection.
+            method.releaseConnection();
+        }
+        
+    }
+}

Added: 
trunk/integration-tests/src/test/java/org/amdatu/test/integration/tests/UserAdminRESTTest.java
==============================================================================
--- (empty file)
+++ 
trunk/integration-tests/src/test/java/org/amdatu/test/integration/tests/UserAdminRESTTest.java
      Fri Dec 17 14:15:20 2010
@@ -0,0 +1,73 @@
+/*
+ Copyright (C) 2010 Amdatu.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.amdatu.test.integration.tests;
+
+import static org.ops4j.pax.exam.CoreOptions.provision;
+
+import java.io.IOException;
+
+import org.amdatu.test.integration.base.ConfigProvider;
+import org.amdatu.test.integration.base.RESTTestBase;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.useradmin.UserAdmin;
+
+ at RunWith(JUnit4TestRunner.class)
+public class UserAdminRESTTest extends RESTTestBase {
+    
+    protected Option provisionBundles() {
+        return provision(
+            paxUserAdmin(),
+            amdatuUserAdminFSStore(),
+            amdatuUserAdminREST());
+    }
+    
+    public Component[] getDependencies(DependencyManager manager) {
+        Component testComponent = getTestComponent(manager);
+        
testComponent.add(manager.createServiceDependency().setService(UserAdmin.class).setRequired(true))
 ;
+        return new Component[] {
+            testComponent            
+        };
+    }
+    
+    @Override
+    protected void addConfig(ConfigProvider configProvider, ConfigurationAdmin 
configAdmin) throws IOException {
+        configProvider.addFSUserAdminConfig(configAdmin);
+    }
+
+    @Test
+    public void testTheRest() throws Exception {
+        // Test the REST interface of the useradmin bundle. First wait before 
it comes up
+        waitForURL(getBaseUrl() + "/users/users", HttpStatus.SC_OK);
+        
+        // -1- Test create user
+        String url = "/users/users/" + ConfigProvider.TEST_USERNAME;
+        invokeRestApi(url,  javax.ws.rs.HttpMethod.PUT, HttpStatus.SC_OK);
+        
+        // -2- Retrieve the user
+        invokeRestApi(url,  javax.ws.rs.HttpMethod.GET, HttpStatus.SC_OK);
+
+        // -3- Delete the user
+        invokeRestApi(url,  javax.ws.rs.HttpMethod.DELETE, HttpStatus.SC_OK);
+    }
+}

Modified: trunk/src/main/resources/conf/felix-config.properties
==============================================================================
--- trunk/src/main/resources/conf/felix-config.properties       (original)
+++ trunk/src/main/resources/conf/felix-config.properties       Fri Dec 17 
14:15:20 2010
@@ -109,6 +109,7 @@
           
reference:file:amdatu-application/org.amdatu.web.rest.wink-${platform.version}.jar
 
 
felix.auto.start.10=reference:file:amdatu-application/org.amdatu.authorization.login.gadget-${platform.version}.jar
 \
           
reference:file:amdatu-application/org.amdatu.authorization.login.service-${platform.version}.jar
 \
+          
reference:file:amdatu-application/org.amdatu.authorization.useradmin.rest-${platform.version}.jar
 \
           
reference:file:amdatu-application/org.amdatu.authentication.oauth.api-${platform.version}.jar
 \
           
reference:file:amdatu-application/org.amdatu.authentication.oauth.client-${platform.version}.jar
 \
           
reference:file:amdatu-application/org.amdatu.authentication.oauth.server-${platform.version}.jar
 \

Reply via email to