org.sakaiproject.nakamura.files.pool.ManageMembersContentPoolServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.nakamura.files.pool.ManageMembersContentPoolServlet.java

Source

/**
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF 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.sakaiproject.nakamura.files.pool;

import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.sakaiproject.nakamura.api.files.FilesConstants.POOLED_CONTENT_USER_EDITOR;
import static org.sakaiproject.nakamura.api.files.FilesConstants.POOLED_CONTENT_USER_MANAGER;
import static org.sakaiproject.nakamura.api.files.FilesConstants.POOLED_CONTENT_USER_VIEWER;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.apache.sling.commons.json.JSONException;
import org.sakaiproject.nakamura.api.doc.BindingType;
import org.sakaiproject.nakamura.api.doc.ServiceBinding;
import org.sakaiproject.nakamura.api.doc.ServiceDocumentation;
import org.sakaiproject.nakamura.api.doc.ServiceMethod;
import org.sakaiproject.nakamura.api.doc.ServiceParameter;
import org.sakaiproject.nakamura.api.doc.ServiceResponse;
import org.sakaiproject.nakamura.api.doc.ServiceSelector;
import org.sakaiproject.nakamura.api.lite.ClientPoolException;
import org.sakaiproject.nakamura.api.lite.Session;
import org.sakaiproject.nakamura.api.lite.StorageClientException;
import org.sakaiproject.nakamura.api.lite.StorageClientUtils;
import org.sakaiproject.nakamura.api.lite.accesscontrol.AccessControlManager;
import org.sakaiproject.nakamura.api.lite.accesscontrol.AccessDeniedException;
import org.sakaiproject.nakamura.api.lite.accesscontrol.AclModification;
import org.sakaiproject.nakamura.api.lite.accesscontrol.Permission;
import org.sakaiproject.nakamura.api.lite.accesscontrol.Permissions;
import org.sakaiproject.nakamura.api.lite.accesscontrol.Security;
import org.sakaiproject.nakamura.api.lite.authorizable.Authorizable;
import org.sakaiproject.nakamura.api.lite.authorizable.AuthorizableManager;
import org.sakaiproject.nakamura.api.lite.authorizable.Group;
import org.sakaiproject.nakamura.api.lite.authorizable.User;
import org.sakaiproject.nakamura.api.lite.content.Content;
import org.sakaiproject.nakamura.api.profile.ProfileService;
import org.sakaiproject.nakamura.api.user.AuthorizableCountChanger;
import org.sakaiproject.nakamura.api.user.BasicUserInfoService;
import org.sakaiproject.nakamura.api.user.UserConstants;
import org.sakaiproject.nakamura.util.ExtendedJSONWriter;
import org.sakaiproject.nakamura.util.ServletUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jcr.RepositoryException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;

@SlingServlet(methods = { "GET", "POST" }, resourceTypes = { "sakai/pooled-content" }, selectors = { "members" })
@Properties(value = { @Property(name = "service.vendor", value = "The Sakai Foundation"),
        @Property(name = "service.description", value = "Manages the Managers, Editors, and Viewers for pooled content.") })
@ServiceDocumentation(name = "Manage Members Content Pool Servlet", okForVersion = "1.2", shortDescription = "List and manage the managers, editors, and viewers for a file in the content pool.", description = "List and manage the managers, editors, and viewers for a file in the content pool.", bindings = {
        @ServiceBinding(type = BindingType.TYPE, bindings = { "sakai/pooled-content" }, selectors = {
                @ServiceSelector(name = "members", description = "Binds to the selector members."),
                @ServiceSelector(name = "detailed", description = "(optional) Provides more detailed profile information."),
                @ServiceSelector(name = "tidy", description = "(optional) Provides formatted JSON output.") }) }, methods = {
                        @ServiceMethod(name = "GET", description = {
                                "Retrieves a list of managers, editors, and viewers for this pooled content item.",
                                "<pre>curl http://localhost:8080/p/hESoXumAT.members.tidy.json</pre>",
                                "<pre>{\n" + "    \"managers\": [{\n" + "        \"hash\": \"suzy\",\n"
                                        + "        \"basic\": {\n" + "            \"access\": \"everybody\",\n"
                                        + "            \"elements\": {\n" + "                \"picture\": {\n"
                                        + "                    \"value\": \"{\\\"name\\\":\\\"256x256_tmp1309269939493.jpg\\\",\\\"_name\\\":\\\"tmp1309269939493.jpg\\\",\\\"_charset_\\\":\\\"utf-8\\\",\\\"selectedx1\\\":0,\\\"selectedy1\\\":3,\\\"selectedx2\\\":85,\\\"selectedy2\\\":88}\"\n"
                                        + "                },\n" + "                \"lastName\": {\n"
                                        + "                    \"value\": \"Queue\"\n" + "                },\n"
                                        + "                \"email\": {\n"
                                        + "                    \"value\": \"suzy@aeroplanesoftware.com\"\n"
                                        + "                },\n" + "                \"firstName\": {\n"
                                        + "                    \"value\": \"Suzy\"\n" + "                }\n"
                                        + "            }\n" + "        },\n" + "        \"rep:userId\": \"suzy\",\n"
                                        + "        \"userid\": \"suzy\",\n" + "        \"counts\": {\n"
                                        + "            \"contactsCount\": 0,\n"
                                        + "            \"membershipsCount\": 0,\n"
                                        + "            \"contentCount\": 3,\n"
                                        + "            \"countLastUpdate\": 1309287542572\n" + "        },\n"
                                        + "        \"sakai:excludeSearch\": false\n" + "    }],\n"
                                        + "    \"editors\": [{\n" + "        \"hash\": \"alice.b.toklas\",\n"
                                        + "        \"basic\": {\n" + "            \"access\": \"everybody\",\n"
                                        + "            \"elements\": {\n" + "                \"picture\": {\n"
                                        + "                    \"value\": \"{\\\"name\\\":\\\"256x256_tmp1309269939493.jpg\\\",\\\"_name\\\":\\\"tmp1309269939493.jpg\\\",\\\"_charset_\\\":\\\"utf-8\\\",\\\"selectedx1\\\":0,\\\"selectedy1\\\":3,\\\"selectedx2\\\":85,\\\"selectedy2\\\":88}\"\n"
                                        + "                },\n" + "                \"lastName\": {\n"
                                        + "                    \"value\": \"Toklas\"\n" + "                },\n"
                                        + "                \"email\": {\n"
                                        + "                    \"value\": \"aliceb@toklas.com\"\n"
                                        + "                },\n" + "                \"firstName\": {\n"
                                        + "                    \"value\": \"Alice\"\n" + "                }\n"
                                        + "            }\n" + "        },\n"
                                        + "        \"rep:userId\": \"aliceb\",\n"
                                        + "        \"userid\": \"aliceb\",\n" + "        \"counts\": {\n"
                                        + "            \"contactsCount\": 0,\n"
                                        + "            \"membershipsCount\": 0,\n"
                                        + "            \"contentCount\": 3,\n"
                                        + "            \"countLastUpdate\": 1309287542572\n" + "        },\n"
                                        + "        \"sakai:excludeSearch\": false\n" + "    }],\n"
                                        + "    \"viewers\": [{\n" + "        \"basic\": {\n"
                                        + "            \"access\": \"everybody\",\n"
                                        + "            \"elements\": {\n" + "                \"lastName\": {\n"
                                        + "                    \"value\": \"User\"\n" + "                },\n"
                                        + "                \"email\": {\n"
                                        + "                    \"value\": \"anon@sakai.invalid\"\n"
                                        + "                },\n" + "                \"firstName\": {\n"
                                        + "                    \"value\": \"Anonymous\"\n" + "                }\n"
                                        + "            }\n" + "        },\n"
                                        + "        \"rep:userId\": \"anonymous\"\n" + "    },\n" + "    {\n"
                                        + "        \"sakai:category\": null,\n"
                                        + "        \"sakai:group-description\": null,\n"
                                        + "        \"sakai:group-id\": \"everyone\",\n"
                                        + "        \"createdBy\": null,\n" + "        \"lastModified\": null,\n"
                                        + "        \"sakai:group-title\": null,\n" + "        \"created\": null,\n"
                                        + "        \"basic\": {\n" + "            \"access\": \"everybody\",\n"
                                        + "            \"elements\": {}\n" + "        },\n"
                                        + "        \"lastModifiedBy\": null,\n"
                                        + "        \"groupid\": \"everyone\",\n" + "        \"counts\": {},\n"
                                        + "        \"sakai:excludeSearch\": false\n" + "    }]\n"
                                        + "}</pre>" }, response = {
                                                @ServiceResponse(code = 200, description = "All processing finished successfully.  Output is in the JSON format."),
                                                @ServiceResponse(code = 500, description = "Any exceptions encountered during processing.") }),
                        @ServiceMethod(name = "POST", description = "Manipulate the member list for a pooled content item.", parameters = {
                                @ServiceParameter(name = ":manager", description = "Set the managers on the ACL of a file."),
                                @ServiceParameter(name = ":editor", description = "Set the editors on the ACL of a file."),
                                @ServiceParameter(name = ":viewer", description = "Set the viewers on the ACL of a file.") }, response = {
                                        @ServiceResponse(code = 200, description = "All processing finished successfully."),
                                        @ServiceResponse(code = 401, description = "POST by anonymous user, or current user doesn't have permission to update this content."),
                                        @ServiceResponse(code = 500, description = "Any exceptions encountered during processing.") }) })
public class ManageMembersContentPoolServlet extends SlingAllMethodsServlet {

    private static final long serialVersionUID = 3385014961034481906L;
    private static final Logger LOGGER = LoggerFactory.getLogger(ManageMembersContentPoolServlet.class);

    private static final Permission PERMISSION_EDITOR = Permissions.CAN_READ.combine(Permissions.CAN_WRITE);

    @Reference
    protected transient ProfileService profileService;
    @Reference
    protected transient BasicUserInfoService basicUserInfoService;
    @Reference
    protected transient AuthorizableCountChanger authorizableCountChanger;

    /**
     * Retrieves the list of members.
     *
     * {@inheritDoc}
     *
     * @see org.apache.sling.api.servlets.SlingSafeMethodsServlet#doGet(org.apache.sling.api.SlingHttpServletRequest,
     *      org.apache.sling.api.SlingHttpServletResponse)
     */
    @Override
    protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
            throws ServletException, IOException {
        try {
            // Get hold of the actual file.
            Resource resource = request.getResource();
            javax.jcr.Session jcrSession = request.getResourceResolver().adaptTo(javax.jcr.Session.class);
            Session session = resource.adaptTo(Session.class);

            AuthorizableManager am = session.getAuthorizableManager();
            AccessControlManager acm = session.getAccessControlManager();
            Content node = resource.adaptTo(Content.class);
            Authorizable thisUser = am.findAuthorizable(session.getUserId());

            if (!acm.can(thisUser, Security.ZONE_CONTENT, resource.getPath(), Permissions.CAN_READ)) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
                return;
            }

            Map<String, Object> properties = node.getProperties();
            String[] managers = (String[]) properties.get(POOLED_CONTENT_USER_MANAGER);
            String[] editors = (String[]) properties.get(POOLED_CONTENT_USER_EDITOR);
            String[] viewers = (String[]) properties.get(POOLED_CONTENT_USER_VIEWER);

            boolean detailed = false;
            for (String selector : request.getRequestPathInfo().getSelectors()) {
                if ("detailed".equals(selector)) {
                    detailed = true;
                    break;
                }
            }

            // Loop over the sets and output it.
            ExtendedJSONWriter writer = new ExtendedJSONWriter(response.getWriter());
            writer.setTidy(ServletUtils.isTidy(request));
            writer.object();
            writer.key("managers");
            writer.array();
            for (String manager : StorageClientUtils.nonNullStringArray(managers)) {
                try {
                    writeProfileMap(jcrSession, am, writer, manager, detailed);
                } catch (AccessDeniedException e) {
                    LOGGER.debug("Skipping private manager [{}]", manager);
                }
            }
            writer.endArray();
            writer.key("editors");
            writer.array();
            for (String editor : StorageClientUtils.nonNullStringArray(editors)) {
                try {
                    writeProfileMap(jcrSession, am, writer, editor, detailed);
                } catch (AccessDeniedException e) {
                    LOGGER.debug("Skipping private editor [{}]", editor);
                }
            }
            writer.endArray();
            writer.key("viewers");
            writer.array();
            for (String viewer : StorageClientUtils.nonNullStringArray(viewers)) {
                try {
                    writeProfileMap(jcrSession, am, writer, viewer, detailed);
                } catch (AccessDeniedException e) {
                    LOGGER.debug("Skipping private viewer [{}]", viewer);
                }
            }
            writer.endArray();
            writer.endObject();
        } catch (JSONException e) {
            response.sendError(SC_INTERNAL_SERVER_ERROR, "Failed to generate proper JSON.");
            LOGGER.error(e.getMessage(), e);
        } catch (StorageClientException e) {
            response.sendError(SC_INTERNAL_SERVER_ERROR, "Failed to generate proper JSON.");
            LOGGER.error(e.getMessage(), e);
        } catch (AccessDeniedException e) {
            response.sendError(SC_INTERNAL_SERVER_ERROR, "Failed to generate proper JSON.");
            LOGGER.error(e.getMessage(), e);
        } catch (RepositoryException e) {
            response.sendError(SC_INTERNAL_SERVER_ERROR, "Failed to generate proper JSON.");
            LOGGER.error(e.getMessage(), e);
        }

    }

    private void writeProfileMap(javax.jcr.Session jcrSession, AuthorizableManager um, ExtendedJSONWriter writer,
            String user, boolean detailed)
            throws JSONException, AccessDeniedException, StorageClientException, RepositoryException {
        Authorizable au = um.findAuthorizable(user);
        if (au != null) {
            ValueMap profileMap;
            if (detailed) {
                profileMap = profileService.getProfileMap(au, jcrSession);
            } else {
                profileMap = new ValueMapDecorator(basicUserInfoService.getProperties(au));
            }
            if (profileMap != null) {
                writer.valueMap(profileMap);
            }
        } else {
            writer.object();
            writer.key("userid");
            writer.value(user);
            writer.endObject();
        }
    }

    /**
     * Manipulate the member list for this file.
     *
     * {@inheritDoc}
     *
     * @see org.apache.sling.api.servlets.SlingAllMethodsServlet#doPost(org.apache.sling.api.SlingHttpServletRequest,
     *      org.apache.sling.api.SlingHttpServletResponse)
     */
    @SuppressWarnings("unchecked")
    @Override
    protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)
            throws ServletException, IOException {
        // fail if anonymous
        String remoteUser = request.getRemoteUser();
        if (User.ANON_USER.equals(remoteUser)) {
            response.sendError(SC_FORBIDDEN, "Anonymous users cannot update content members.");
            return;
        }
        Session session = null;
        boolean releaseSession = false;
        try {
            Resource resource = request.getResource();
            session = resource.adaptTo(Session.class);
            Content pooledContent = resource.adaptTo(Content.class);
            AccessControlManager accessControlManager = session.getAccessControlManager();
            AuthorizableManager authorizableManager = session.getAuthorizableManager();
            User thisUser = authorizableManager.getUser();
            if (!accessControlManager.can(thisUser, Security.ZONE_CONTENT, pooledContent.getPath(),
                    Permissions.CAN_READ)) {
                response.sendError(SC_FORBIDDEN, "Insufficient permission to read this content.");
            }

            Map<String, Object> properties = pooledContent.getProperties();
            String[] managers = StorageClientUtils
                    .nonNullStringArray((String[]) properties.get(POOLED_CONTENT_USER_MANAGER));
            String[] editors = StorageClientUtils
                    .nonNullStringArray((String[]) properties.get(POOLED_CONTENT_USER_EDITOR));
            String[] viewers = StorageClientUtils
                    .nonNullStringArray((String[]) properties.get(POOLED_CONTENT_USER_VIEWER));

            Set<String> managerSet = Sets.newHashSet(managers);
            Set<String> editorSet = Sets.newHashSet(editors);
            Set<String> viewerSet = Sets.newHashSet(viewers);

            List<String> removeViewers = Arrays
                    .asList(StorageClientUtils.nonNullStringArray(request.getParameterValues(":viewer@Delete")));
            List<String> removeManagers = Arrays
                    .asList(StorageClientUtils.nonNullStringArray(request.getParameterValues(":manager@Delete")));
            List<String> removeEditors = Arrays
                    .asList(StorageClientUtils.nonNullStringArray(request.getParameterValues(":editor@Delete")));
            List<String> addViewers = Arrays
                    .asList(StorageClientUtils.nonNullStringArray(request.getParameterValues(":viewer")));
            List<String> addManagers = Arrays
                    .asList(StorageClientUtils.nonNullStringArray(request.getParameterValues(":manager")));
            List<String> addEditors = Arrays
                    .asList(StorageClientUtils.nonNullStringArray(request.getParameterValues(":editor")));

            // get rid of blank values and trim non-blank values
            removeViewers = sanitizeUsernames(removeViewers);
            removeManagers = sanitizeUsernames(removeManagers);
            removeEditors = sanitizeUsernames(removeEditors);
            addViewers = sanitizeUsernames(addViewers);
            addManagers = sanitizeUsernames(addManagers);
            addEditors = sanitizeUsernames(addEditors);

            /*
             * limit the add and remove lists to only those operations that should be performed:
             * 
             * 1. If you have both an add and a remove operation, the remove takes precedence
             * 2. If you add a user to a role to which they are already in, no operation is performed
             * 3. If you remove a user from a role to which they don't belong, no operation is performed
             */

            // 1.
            addManagers.removeAll(removeManagers);
            addEditors.removeAll(removeEditors);
            addViewers.removeAll(removeViewers);

            // 2.
            addManagers.removeAll(managerSet);
            addEditors.removeAll(editorSet);
            addEditors.removeAll(managerSet);
            addViewers.removeAll(viewerSet);
            addViewers.removeAll(editorSet);
            addViewers.removeAll(managerSet);

            // 3.
            removeManagers.retainAll(managerSet);
            removeEditors.retainAll(editorSet);
            removeViewers.retainAll(viewerSet);

            // don't continue any further if there are no relevant operations to perform
            if (addManagers.isEmpty() && removeManagers.isEmpty() && addEditors.isEmpty() && removeEditors.isEmpty()
                    && addViewers.isEmpty() && removeViewers.isEmpty()) {
                response.setStatus(SC_OK);
                return;
            }

            //Checking for non-managers
            if (!accessControlManager.can(thisUser, Security.ZONE_CONTENT, pooledContent.getPath(),
                    Permissions.CAN_WRITE)) {
                if (!addManagers.isEmpty()) {
                    response.sendError(SC_FORBIDDEN, "Non-managers may not add managers to content.");
                    return;
                }

                for (String name : removeManagers) {
                    // asking to remove managers who don't exist is harmless
                    if (managerSet.contains(name)) {
                        response.sendError(SC_FORBIDDEN, "Non-managers may not remove managers from content.");
                        return;
                    }
                }

                if (addViewers.contains(User.ANON_USER) || addViewers.contains(Group.EVERYONE)) {
                    response.sendError(SC_FORBIDDEN,
                            "Non-managers may not add 'anonymous' or 'everyone' as viewers.");
                    return;
                }

                if (addEditors.contains(User.ANON_USER) || addEditors.contains(Group.EVERYONE)) {
                    response.sendError(SC_FORBIDDEN,
                            "Non-managers may not add 'anonymous' or 'everyone' as editors.");
                    return;
                }

                for (String name : removeViewers) {
                    if (!thisUser.getId().equals(name)) {
                        Authorizable viewer = authorizableManager.findAuthorizable(name);
                        if (viewer != null && !accessControlManager.can(thisUser, Security.ZONE_AUTHORIZABLES, name,
                                Permissions.CAN_WRITE)) {
                            response.sendError(SC_FORBIDDEN,
                                    "Non-managers may not remove any viewer other than themselves or a group which they manage.");
                            return;
                        }
                    }
                }

                // the request has passed all the rules that govern non-manager users
                // so we'll grant an administrative session
                session = session.getRepository().loginAdministrative();
                releaseSession = true;
            }
            List<AclModification> aclModifications = Lists.newArrayList();

            // apply the removals before the adds, because the permission grants should take
            // precedence
            for (String removeManager : removeManagers) {
                AclModification.removeAcl(true, Permissions.CAN_MANAGE, removeManager, aclModifications);
            }

            for (String removeEditor : removeEditors) {
                AclModification.removeAcl(true, PERMISSION_EDITOR, removeEditor, aclModifications);
            }

            for (String removeViewer : removeViewers) {
                AclModification.removeAcl(true, Permissions.CAN_READ, removeViewer, aclModifications);
            }

            for (String addManager : addManagers) {
                AclModification.addAcl(true, Permissions.CAN_MANAGE, addManager, aclModifications);
            }

            for (String addEditor : addEditors) {
                AclModification.addAcl(true, PERMISSION_EDITOR, addEditor, aclModifications);
            }

            for (String addViewer : addViewers) {
                AclModification.addAcl(true, Permissions.CAN_READ, addViewer, aclModifications);
            }

            // apply the operations to the final set of content roles
            managerSet.removeAll(removeManagers);
            managerSet.addAll(addManagers);
            editorSet.removeAll(removeEditors);
            editorSet.addAll(addEditors);
            viewerSet.removeAll(removeViewers);
            viewerSet.addAll(addViewers);

            updateContentMembers(session, pooledContent, viewerSet, managerSet, editorSet);
            updateContentAccess(session, pooledContent, aclModifications);

            this.authorizableCountChanger.notify(UserConstants.CONTENT_ITEMS_PROP, addViewers, addEditors,
                    addManagers, removeViewers, removeEditors, removeManagers);

            response.setStatus(SC_OK);

        } catch (StorageClientException e) {
            LOGGER.error(e.getMessage());
            response.sendError(SC_INTERNAL_SERVER_ERROR, "StorageClientException: " + e.getLocalizedMessage());
        } catch (AccessDeniedException e) {
            response.sendError(SC_FORBIDDEN,
                    "Insufficient permission to update content members at " + request.getRequestURI());
        } finally {
            if (session != null && releaseSession) {
                try {
                    session.logout();
                } catch (ClientPoolException e) {
                    LOGGER.error(e.getMessage());
                }
            }
        }
    }

    /**
     * Given a list of raw Strings, clean them to only include valid usernames. The returned
     * array should be a list of trimmed, non-null, non-empty strings that should be limited
     * to only those that were considered valid syntactic usernames.
     * 
     * @param usernames
     * @return A list of syntactically correct usernames.
     */
    private List<String> sanitizeUsernames(Iterable<String> usernames) {
        List<String> result = new LinkedList<String>();
        for (Iterator<String> i = usernames.iterator(); i.hasNext();) {
            String username = i.next();
            if (StringUtils.isNotBlank(username)) {
                result.add(username.trim());
            }
        }
        return result;
    }

    private void updateContentMembers(Session session, Content content, Set<String> viewerSet,
            Set<String> managerSet, Set<String> editorSet) throws StorageClientException, AccessDeniedException {
        content.setProperty(POOLED_CONTENT_USER_VIEWER, viewerSet.toArray(new String[viewerSet.size()]));
        content.setProperty(POOLED_CONTENT_USER_MANAGER, managerSet.toArray(new String[managerSet.size()]));
        content.setProperty(POOLED_CONTENT_USER_EDITOR, editorSet.toArray(new String[editorSet.size()]));
        LOGGER.debug("Set Managers to {}", Arrays.toString(managerSet.toArray(new String[managerSet.size()])));
        LOGGER.debug("Set Editors to {}", Arrays.toString(editorSet.toArray(new String[editorSet.size()])));
        LOGGER.debug("Set Viewers to {}", Arrays.toString(viewerSet.toArray(new String[managerSet.size()])));
        session.getContentManager().update(content);
    }

    private void updateContentAccess(Session session, Content content, List<AclModification> aclModifications)
            throws StorageClientException, AccessDeniedException {
        LOGGER.debug("ACL Modifications {}",
                Arrays.toString(aclModifications.toArray(new AclModification[aclModifications.size()])));
        session.getAccessControlManager().setAcl(Security.ZONE_CONTENT, content.getPath(),
                aclModifications.toArray(new AclModification[aclModifications.size()]));
    }

}