Skip to content

Commit

Permalink
Merge pull request sakaiproject#95 from adrianfish/SAK-22892
Browse files Browse the repository at this point in the history
SAK-22892 Users can now be added to sites via JSON
  • Loading branch information
adrianfish committed Jan 28, 2015
2 parents bff2ddc + 5323c16 commit 23efc2d
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 0 deletions.
5 changes: 5 additions & 0 deletions entitybroker/core-providers/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
12 changes: 12 additions & 0 deletions entitybroker/core-providers/src/java/membership.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ membership.view.list = By default, retrieves a list of all site memberships for
membership.action.site = The URL format is "/membership/site/:SITE_ID:". <br/> \
(GET) will return all memberships in the specified site. <br/> \
(POST) will add one or more site memberships via an array of "userSearchValues" request parameters (each of which is a user ID, user EID, or email address), along with a "memberRole" string (default for joinable site memberships is the default joiner role) and "active" Boolean value (default is true). Response headers may include "x-warning-not-found" (for a list of invalid userSearchValues) and "x-warning-already-members" (for a list of userSearchValues which were already site members).
membership.action.sitebyjson = The URL format is "/membership/sitebyjson/:SITE_ID:". <br/> \
(POST) will add one or more site memberships via an chunk of JSON supplied in the 'json' request parameter.<br/> \
<br/>\
The JSON needs to be formatted like this: <br/> \
<br/>\
[ {"id": "user1", "role": "access"}, {"id": "user2\","role": "maintain"} ]<br/> \
<br/>\
Three response headers will be set:<br/> \
<br/>\
<b>x-success-count</b>, which contains the number of successful additions<br/> \
<b>x-warning-not-found</b>, which contains a comma separated list of user ids which didn't exist<br/> \
<b>x-warning-already-members</b>, which contains a comma separated list of user ids which were already in the site
membership.action.group = The URL format is "/membership/group/:groupId:" <br/> \
(GET) will return all memberships for a group. <br/> \
(POST) will update user group membership. Mandatory POST Parameters: 'userIds' a comma separated list of users (eid or id). \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

package org.sakaiproject.entitybroker.providers;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand Down Expand Up @@ -64,6 +65,11 @@
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.site.api.SiteService.SelectionType;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
* This provides access to memberships as entities
*
Expand Down Expand Up @@ -205,6 +211,7 @@ public ActionReturn handleSiteMemberships(EntityView view, Map<String, Object> p
"siteId must be set in order to get site memberships, set in params or in the URL /membership/site/siteId");
}
}

String locationReference = "/site/" + siteId;

Map<String, String> extraResponseHeaders = null;
Expand Down Expand Up @@ -309,6 +316,113 @@ public List <EntityData> getMembershipRoles(EntityView view, Map<String, Object>
return getEntities(new EntityReference(PREFIX, ""), s);

}

/**
* Special handler for JSON uploads of site membership data. Takes a parameter 'json' which
* should contain json of the form:
*
* [ {"id": "user1", "role": "access"},{"id": "user2","role": "maintain"} ]
*/
@EntityCustomAction(action = "sitebyjson", viewKey = EntityView.VIEW_NEW)
public ActionReturn handleSiteJsonUpload(EntityView view, Map<String, Object> params) {

if (log.isDebugEnabled()) {
log.debug("handleSiteJsonUpload method=" + view.getMethod() + ", params=" + params);
}

String siteId = view.getPathSegment(2);
if (siteId == null) {
siteId = (String) params.get("siteId");
if (siteId == null) {
throw new IllegalArgumentException(
"siteId must be set in order to get site memberships, set in params or in the URL /membership/site/siteId");
}
}

String locationReference = "/site/" + siteId;

String json = (String) params.get("json");
if (json == null) {
throw new IllegalArgumentException(
"The membership JSON data must be supplied as a POST parameter named 'json'.");
}

Map<String, String> extraResponseHeaders = new HashMap<String, String>(3);

ObjectMapper mapper = new ObjectMapper();
List<JsonUser> memberships;

try {
memberships = mapper.readValue(json.getBytes(), new TypeReference<List<JsonUser>>() { });
} catch(JsonParseException jpe) {
throw new IllegalArgumentException("The supplied JSON was invalid. Have a look at http://www.json.org/.");
} catch(JsonMappingException jpe) {
throw new IllegalArgumentException("The supplied JSON was invalid. Take a look at /direct/membership/describe for the correct structure.");
} catch (IOException e) {
throw new IllegalArgumentException("Failed to read the supplied JSON.");
}

// Collect the users into roles
Map<String, List<String>> usersToRoleMap = new HashMap<String, List<String>>();
for (JsonUser user : memberships) {
String id = user.getId();
String role = user.getRole();

if (usersToRoleMap.containsKey(role)) {
usersToRoleMap.get(role).add(id);
} else {
List<String> users = new ArrayList<String>();
users.add(id);
usersToRoleMap.put(role, users);
}
}

Map<String, Object> localParams = new HashMap<String, Object>(2);

// Compile results from createBatchMemberships. We'll return these in the response.
int successCount = 0;
StringBuilder notFoundBuilder = new StringBuilder();
StringBuilder alreadyMemberBuilder = new StringBuilder();

Iterator<String> memberRoles = usersToRoleMap.keySet().iterator();
while (memberRoles.hasNext()) {
String memberRole = memberRoles.next();
localParams.put("userSearchValues", usersToRoleMap.get(memberRole));
localParams.put("memberRole", memberRole);
Map<String, String> response = createBatchMemberships(view, localParams, locationReference);

String successCountString = response.get("x-success-count");
if (successCountString != null) {
try {
successCount += Integer.parseInt(successCountString);
} catch (NumberFormatException nfe) {
log.error("x-success-count was not a number. successCount was not increased.");
}
}

String nf = response.get("x-warning-not-found");
if (nf != null) {
notFoundBuilder.append(nf);
if (memberRoles.hasNext()) {
notFoundBuilder.append(", ");
}
}

String am = response.get("x-warning-already-members");
if (am != null) {
alreadyMemberBuilder.append(am);
if (memberRoles.hasNext()) {
alreadyMemberBuilder.append(", ");
}
}
}

extraResponseHeaders.put("x-success-count", String.valueOf(successCount));
extraResponseHeaders.put("x-warning-not-found", notFoundBuilder.toString());
extraResponseHeaders.put("x-warning-already-members", alreadyMemberBuilder.toString());

return new ActionReturn("", extraResponseHeaders);
}

@EntityCustomAction(action = "group", viewKey = "")
public List<EntityData> getGroupMemberships(EntityView view, Map<String, Object> params) {
Expand Down Expand Up @@ -954,6 +1068,8 @@ protected List<String> getListFromValue(Object paramValue) {
stringList = Arrays.asList((String[]) paramValue);
} else if (paramValue instanceof String) {
stringList.add((String) paramValue);
} else if(paramValue.getClass().isInstance(new ArrayList<String>())) {
return (List<String>) paramValue;
}
}
return stringList;
Expand Down Expand Up @@ -1110,5 +1226,26 @@ private void checkSiteSecurity(String siteId) {
throw new SecurityException("Admin site membership changes are disabled for security protection against CSRF, you must use the sakai admin UI or enable changes in your sakai config file using "+ADMIN_SITE_CHANGE_ALLOWED+"=true");
}
}

public static class JsonUser {
private String id = "";
private String role = "";

public JsonUser() {}
public JsonUser(String id, String role) {
this.id = id;
this.role = role;
}

public String getId() { return id; }
public void setId(String id) {
this.id = id;
}

public String getRole() { return role; }
public void setRole(String role) {
this.role = role;
}
}

}

0 comments on commit 23efc2d

Please sign in to comment.