org.sakaiproject.lessonbuildertool.service.LessonsGradeInfoProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.lessonbuildertool.service.LessonsGradeInfoProvider.java

Source

/**********************************************************************************
 * $URL: https://newtools.oirt.rutgers.edu:8443/repos/sakai2.x/sakai/trunk/assignment/assignment-impl/impl/src/java/org/sakaiproject/assignment/impl/AssignmentGradeInfoProvider.java $
 * $Id: AssignmentGradeInfoProvider.java 4492 2013-03-22 15:02:00Z willkara $
 ***********************************************************************************
 *
 * Copyright (c) 2011 The Sakai Foundation
 *
 * Licensed under the Educational Community 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.osedu.org/licenses/ECL-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.lessonbuildertool.service;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.authz.api.AuthzGroup;
import org.sakaiproject.authz.api.AuthzGroupService;
import org.sakaiproject.authz.api.SecurityService;
import org.sakaiproject.db.cover.SqlService;
import org.sakaiproject.lessonbuildertool.SimplePage;
import org.sakaiproject.lessonbuildertool.SimplePageItem;
import org.sakaiproject.lessonbuildertool.model.SimplePageToolDao;
import org.sakaiproject.memory.api.Cache;
import org.sakaiproject.memory.api.MemoryService;
import org.sakaiproject.service.gradebook.shared.ExternalAssignmentProvider;
import org.sakaiproject.service.gradebook.shared.GradebookExternalAssessmentService;
import org.sakaiproject.user.api.User;
import org.sakaiproject.user.cover.UserDirectoryService;
import org.sakaiproject.lessonbuildertool.service.LessonsAccess;
import org.sakaiproject.lessonbuildertool.service.LessonsAccess.Path;

import java.util.*;

public class LessonsGradeInfoProvider implements ExternalAssignmentProvider {

    private Log log = LogFactory.getLog(LessonsGradeInfoProvider.class);

    // caching
    private static Cache cache = null;
    // currently using 10 sec. The real goal is to prevent continual
    // reevaluation of items as we follow different paths. I.e. we mostly
    // care about it during a single transaction. But I'm using the normal
    // default of 10 min
    protected static final int DEFAULT_EXPIRATION = 60 * 10;

    // Sakai Service Beans
    private GradebookExternalAssessmentService geaService;
    private SimplePageToolDao dao;
    private AuthzGroupService authzGroupService;
    private SecurityService securityService;
    private MemoryService memoryService;
    private LessonsAccess lessonsAccess;

    public void init() {
        cache = memoryService.newCache("org.sakaiproject.lessonbuildertool.service.LessonsGradeInfoProvider.cache");
        log.info("INIT and register LessonsGradeInfoProvider");
        geaService.registerExternalAssignmentProvider(this);
    }

    public void destroy() {
        log.info("DESTROY and unregister LessonsGradeInfoProvider");
        geaService.unregisterExternalAssignmentProvider(getAppKey());
        cache.close();
        cache = null;
    }

    public String getAppKey() {
        return "Lesson Builder";
    }

    // general note:
    // this code needs to be efficient for large sites
    // I do my best to use bulk operations

    // ids look like lesson-builder:comment:NNN
    // throughout this class you'll see code to find
    // the item from the id by finding the item number
    // at the end and that looking up by ID

    public boolean isAssignmentDefined(String id) {
        if (!id.startsWith("lesson-builder:"))
            return false;
        int i = id.lastIndexOf(":");
        if (i < 0)
            return false;
        String prefix = id.substring(0, i);
        String itemNum = id.substring(i + 1);

        if ("lesson-builder".equals(prefix)) {
            SimplePage page = null;
            long pageId = 0;
            try {
                pageId = Long.parseLong(itemNum);
                page = dao.getPage(pageId);
                if (page == null)
                    return false;
            } catch (Exception e) {
                return false;
            }
            return true;
        }

        long itemId = 0;
        try {
            itemId = Long.parseLong(itemNum);
            if (dao.findItem(itemId) == null)
                return false;
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    boolean isPathSetGrouped(Set<Path> paths) {
        for (Path path : paths) {
            if (path.groups == null)
                return false;
        }
        return true;
    }

    // is access to this item restricted by group access
    public boolean isAssignmentGrouped(String id) {
        if (!id.startsWith("lesson-builder:"))
            return false;
        int i = id.lastIndexOf(":");
        if (i < 0)
            return false;
        String prefix = id.substring(0, i);
        String itemNum = id.substring(i + 1);

        if ("lesson-builder".equals(prefix)) {
            SimplePage page = null;
            long pageId = 0;
            try {
                pageId = Long.parseLong(itemNum);
                page = dao.getPage(pageId);
                if (page == null)
                    return false;
            } catch (Exception e) {
                return false;
            }
            return isPathSetGrouped(lessonsAccess.getPagePaths(pageId, false));

        }

        SimplePageItem item = null;
        long itemId = 0;
        try {
            itemId = Long.parseLong(itemNum);
            item = dao.findItem(itemId);
            if (item == null)
                return false;
        } catch (Exception e) {
            return false;
        }

        return isPathSetGrouped(lessonsAccess.getItemPaths(itemId));

    }

    // my best estimate is about 100 bytes / call. The maximum likely chain is 
    // 100. So that could put 10K on the stack. With 1 G stacks I think that's OK

    /*
      main entry to this is getItemGroups(item, [])
      find all the groups allowed to use this item
        
      null means no constraints
      [], i.e. empty set, means impossible
    */

    boolean isUserInPath(String userId, Set<Path> paths, String siteId) {
        nextpath: for (Path path : paths) {
            if (path.groups == null) // no constraint
                return true;

            for (Set<String> groupIds : path.groups) {
                ArrayList<String> groups = new ArrayList<String>();
                for (String groupId : groupIds)
                    groups.add("/site/" + siteId + "/group/" + groupId);

                List<AuthzGroup> matched = authzGroupService.getAuthzUserGroupIds(groups, userId);
                // have to at least one
                if (matched.size() < 1)
                    continue nextpath;
            }
            // matched all in path
            return true;
        }
        return false;
    }

    Set<String> usersInPath(Collection<String> userIds, Set<Path> paths, String siteId) {

        Set<String> retUsers = new HashSet<String>();

        for (Path path : paths) {
            if (path.groups == null) // no constraint
                return new HashSet<String>(userIds);

            // users for this path. It's users who are in all the groups
            Set<String> pathUsers = new HashSet<String>(userIds);

            for (Set<String> groupIds : path.groups) {
                Set<String> groups = new HashSet<String>();
                for (String groupId : groupIds)
                    groups.add("/site/" + siteId + "/group/" + groupId);
                Set<String> okUsers = new HashSet<String>(authzGroupService.getAuthzUsersInGroups(groups));
                pathUsers.retainAll(okUsers);
            }

            // these users are in all path elements, add them to be returned
            retUsers.addAll(pathUsers);
        }

        return retUsers;
    }

    public boolean isAssignmentVisible(String id, String userId) {
        int i = id.lastIndexOf(":");
        if (i < 0)
            return false;
        String prefix = id.substring(0, i);
        String itemNum = id.substring(i + 1);
        long itemId = 0;
        try {
            itemId = Long.parseLong(itemNum);
        } catch (Exception e) {
            return false;
        }

        Set<Path> paths = null;

        // if it's a page rather than an item
        if ("lesson-builder".equals(prefix))
            paths = lessonsAccess.getPagePaths(itemId, false);
        else {
            SimplePageItem item = dao.findItem(itemId);
            paths = lessonsAccess.getItemPaths(itemId);
            itemId = item.getPageId();
        }

        // itemId is now the pageId, no matter what kind of thing this is

        // there are two things to check. One is whether the user is in the site.
        // the other is whether the item is grouped. If so, is the user in that group

        SimplePage page = dao.getPage(itemId);
        String siteId = page.getSiteId();
        String ref = "/site/" + siteId;
        boolean visible = securityService.unlock(userId, SimplePage.PERMISSION_LESSONBUILDER_READ, ref);
        if (!visible)
            return false;

        // can access the site. If not grouped, we're done.

        if (!isPathSetGrouped(paths))
            return true;

        return isUserInPath(userId, paths, siteId);
    }

    public List<String> getExternalAssignmentsForCurrentUser(String gradebookUid) {

        List<String> ret = new ArrayList<String>();

        String ref = "/site/" + gradebookUid;
        boolean visible = securityService.unlock(SimplePage.PERMISSION_LESSONBUILDER_READ, ref);
        if (!visible)
            return ret;

        String userId = UserDirectoryService.getCurrentUser().getId();

        List<SimplePageItem> externalItems = dao.findGradebookItems(gradebookUid);
        for (SimplePageItem item : externalItems) {
            Set<Path> paths = lessonsAccess.getItemPaths(item.getId());
            if (isUserInPath(userId, paths, gradebookUid)) {
                if (item.getGradebookId() != null)
                    ret.add(item.getGradebookId());
                if (item.getAltGradebook() != null)
                    ret.add(item.getAltGradebook());
            }
        }

        List<SimplePage> externalPages = dao.findGradebookPages(gradebookUid);
        for (SimplePage page : externalPages) {
            Set<Path> paths = lessonsAccess.getPagePaths(page.getPageId(), false);
            if (isUserInPath(userId, paths, gradebookUid)) {
                if (page.getGradebookPoints() != null)
                    ret.add("lesson-builder:" + page.getPageId());
            }
        }

        // list of items we have modified the group membership for. We have to override the
        // value returned by the tool itself
        Map<String, ArrayList<String>> otherTools = getExternalAssigns(gradebookUid);
        for (Map.Entry<String, ArrayList<String>> entry : otherTools.entrySet()) {
            if (entry.getValue() == null)
                ret.add(entry.getKey());
            else {
                List<AuthzGroup> matched = authzGroupService.getAuthzUserGroupIds(entry.getValue(), userId);
                if (matched.size() > 0)
                    ret.add(entry.getKey());
            }
        }
        return ret;
    }

    public Map<String, List<String>> getAllExternalAssignments(String gradebookUid, Collection<String> studentIds) {
        //System.out.println("isassignmentgrouped lesson-builder:1788 " + isAssignmentGrouped("lesson-builder:1788"));
        //System.out.println("isassignmentgrouped lesson-builder:comment:7289 " + isAssignmentGrouped("lesson-builder:comment:7289"));
        //System.out.println("isUserInPath " + isUserInPath("c08d3ac9-c717-472a-ad91-7ce0b434f42f", lessonsAccess.getPagePaths(1788L,false),"60c04ab8-40e5-4eb6-9f8d-7006ed023109"));
        //System.out.println("isUserInPath " + isUserInPath("c08d3ac9-c717-472a-ad91-7ce0b434f42f", lessonsAccess.getItemPaths(7289L),"60c04ab8-40e5-4eb6-9f8d-7006ed023109"));
        //HashSet<String> userset = new HashSet<String>();
        //userset.add("c08d3ac9-c717-472a-ad91-7ce0b434f42f");
        //userset.add("9d1a25ba-4735-48c4-bd5e-ff329f9f7749");

        //System.out.println("usersInPath " + usersInPath(userset, lessonsAccess.getPagePaths(1788L,false),"60c04ab8-40e5-4eb6-9f8d-7006ed023109"));
        //System.out.println("userInPath " + usersInPath(userset, lessonsAccess.getItemPaths(7289L),"60c04ab8-40e5-4eb6-9f8d-7006ed023109"));
        //System.out.println(isAssignmentVisible("lesson-builder:1788", "c08d3ac9-c717-472a-ad91-7ce0b434f42f"));
        //System.out.println(isAssignmentVisible("lesson-builder:comment:7289", "c08d3ac9-c717-472a-ad91-7ce0b434f42f"));
        //System.out.println(getExternalAssignmentsForCurrentUser("60c04ab8-40e5-4eb6-9f8d-7006ed023109"));

        Map<String, List<String>> allExternals = new HashMap<String, List<String>>();

        String ref = "/site/" + gradebookUid;
        List<User> allowedUsers = securityService.unlockUsers(SimplePage.PERMISSION_LESSONBUILDER_READ, ref);
        if (allowedUsers.size() < 1)
            return allExternals; // no users allowed, nothing to do
        List<String> allowedIds = new ArrayList<String>();
        for (User user : allowedUsers)
            allowedIds.add(user.getId());

        // remove any user without lesson builder read in the site
        studentIds.retainAll(allowedIds);
        for (String studentId : studentIds) {
            allExternals.put(studentId, new ArrayList<String>());
        }

        List<SimplePageItem> externalItems = dao.findGradebookItems(gradebookUid);
        for (SimplePageItem item : externalItems) {

            Set<Path> paths = lessonsAccess.getItemPaths(item.getId());
            Set<String> users = usersInPath(studentIds, paths, gradebookUid);

            // add this assignment to all users that are in the groups
            for (String userId : users)
                if (allExternals.containsKey(userId)) {
                    if (item.getGradebookId() != null)
                        allExternals.get(userId).add(item.getGradebookId());
                    if (item.getAltGradebook() != null)
                        allExternals.get(userId).add(item.getAltGradebook());
                }

        }

        List<SimplePage> externalPages = dao.findGradebookPages(gradebookUid);
        for (SimplePage page : externalPages) {

            Set<Path> paths = lessonsAccess.getPagePaths(page.getPageId(), false);
            Set<String> users = usersInPath(studentIds, paths, gradebookUid);

            // add this assignment to all users that are in the groups
            for (String userId : users)
                if (allExternals.containsKey(userId)) {
                    if (page.getGradebookPoints() != null)
                        allExternals.get(userId).add("lesson-builder:" + page.getPageId());
                }
        }

        // now handle other tools. If we modified their groups, we need to find the original groups
        // and return the users that match those groups
        // find list of items we've modified
        // map of external ID to list of original groups for that item
        Map<String, ArrayList<String>> otherTools = getExternalAssigns(gradebookUid);
        // for each externalId
        for (Map.Entry<String, ArrayList<String>> entry : otherTools.entrySet()) {
            String externalId = entry.getKey();
            // if no group restriction
            if (entry.getValue() == null) {
                // add this item to all students
                for (String userId : studentIds)
                    if (allExternals.containsKey(userId))
                        allExternals.get(userId).add(externalId);
            } else {
                // otherwise find users that are in the groups
                Set<String> okUsers = new HashSet<String>(
                        authzGroupService.getAuthzUsersInGroups(new HashSet<String>(entry.getValue())));
                okUsers.retainAll(studentIds);
                // and add this item just to them
                for (String u : okUsers)
                    if (allExternals.containsKey(u)) {
                        allExternals.get(u).add(externalId);
                    }
            }
        }
        //System.out.println("getAllExternalAssignments " + studentIds + " " + allExternals);

        return allExternals;
    }

    // for the moment just our own assignments
    public List<String> getAllExternalAssignments(String gradebookUid) {
        List<String> ret = new ArrayList<String>();

        List<SimplePageItem> externalItems = dao.findGradebookItems(gradebookUid);
        for (SimplePageItem item : externalItems) {
            if (item.getGradebookId() != null)
                ret.add(item.getGradebookId());
            if (item.getAltGradebook() != null)
                ret.add(item.getAltGradebook());
        }
        List<SimplePage> externalPages = dao.findGradebookPages(gradebookUid);
        for (SimplePage page : externalPages) {
            if (page.getGradebookPoints() != null)
                ret.add("lesson-builder:" + page.getPageId());
        }

        return ret;
    }

    // return list of items where we've replaced the group with our own
    // returns map externalId -> group list
    public Map<String, ArrayList<String>> getExternalAssigns(String gradebookUid) {
        Map<String, ArrayList<String>> ret = new HashMap<String, ArrayList<String>>();

        // should this use the Dao? Not clear that it makes sense in the Lessons dao.
        // I don't see a good approach in gradebook to do this. We could fetch all assignments
        // and check externally maintained, but this is performace critical, so I hate to do that.

        // find the external items in the gradebook. Unfortunately we can have
        // items in the lesson_builder_groups table that aren't in the gradebook

        String sql = "select b.external_id from GB_GRADEBOOK_T a, GB_GRADABLE_OBJECT_T b where a.gradebook_uid=? and a.id=b.GRADEBOOK_ID and b.EXTERNALLY_MAINTAINED=1";
        Object[] fields = new Object[1];
        fields[0] = gradebookUid;
        List<String> externalIds = SqlService.dbRead(sql, fields, null);
        if (externalIds == null)
            return null;
        Set<String> externalIdSet = new HashSet<String>(externalIds);

        // map sakaiId to group list as text
        Map<String, String> externals = dao.getExternalAssigns(gradebookUid);

        for (Map.Entry<String, String> entry : externals.entrySet()) {
            String sakaiId = entry.getKey();
            String externalId = null;
            // only handle item types for which we actually hack on the group membership
            // internal LB items are handled elsewhere, so this is just assignments and Samigo
            // assignment 2 and Forums use gradebook items that aren't external, so group
            // membership isn't relevant. We have to map from format of sakaiId to format
            // of gradebook's external ID. GB uses just the item number for Samigo, and
            // the full reference for Assignment
            if (sakaiId.startsWith("/sam_pub/"))
                externalId = sakaiId.substring("/sam_pub/".length());
            else if (sakaiId.startsWith("/assignment/"))
                externalId = "/assignment/a/" + gradebookUid + "/" + sakaiId.substring("/assignment/".length());
            // sam and assignment is all we deal with, so anything else
            // in new format we skip. Following is old format sakaiid's where
            // the ID numbers were included with no type information
            else if (sakaiId.startsWith("/"))
                continue;
            else if (sakaiId.indexOf("-") >= 0) // old format assignment
                externalId = "/assignment/a/" + gradebookUid + "/" + sakaiId;
            else
                externalId = sakaiId; // old format Samigo
            // in our groups table but not in gradebook, don't need it
            if (!externalIdSet.contains(externalId))
                continue;

            // have external ID, get group list
            ArrayList<String> groups = null;
            String groupString = entry.getValue();
            if (groupString != null && !groupString.equals("")) {
                groups = new ArrayList<String>();
                String[] groupArray = groupString.split(",");
                for (String groupId : groupArray) {
                    groups.add("/site/" + gradebookUid + "/group/" + groupId);
                }
            }
            ret.put(externalId, groups);
        }
        return ret;
    }

    public void setGradebookExternalAssessmentService(GradebookExternalAssessmentService geaService) {
        this.geaService = geaService;
    }

    public GradebookExternalAssessmentService getGradebookExternalAssessmentService() {
        return geaService;
    }

    public void setAuthzGroupService(AuthzGroupService authzGroupService) {
        this.authzGroupService = authzGroupService;
    }

    public AuthzGroupService getAuthzGroupService() {
        return authzGroupService;
    }

    public void setSecurityService(SecurityService securityService) {
        this.securityService = securityService;
    }

    public SecurityService getSecurityService() {
        return securityService;
    }

    public void setSimplePageToolDao(SimplePageToolDao s) {
        dao = s;
    }

    public void setMemoryService(MemoryService m) {
        memoryService = m;
    }

    public void setLessonsAccess(LessonsAccess l) {
        lessonsAccess = l;
    }

}