/**********************************************************************************
* $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 java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Arrays;
import java.util.Date;
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.authz.api.SecurityAdvisor;
import org.sakaiproject.entity.api.Reference;
import org.sakaiproject.entity.api.EntityManager;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.exception.PermissionException;
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.service.gradebook.shared.ExternalAssignmentProvider;
import org.sakaiproject.service.gradebook.shared.GradebookExternalAssessmentService;
import org.sakaiproject.tool.api.SessionManager;
import org.sakaiproject.tool.api.SessionManager;
import org.sakaiproject.user.cover.UserDirectoryService;
import org.sakaiproject.user.api.User;
import org.sakaiproject.db.cover.SqlService;
import org.sakaiproject.db.api.SqlReader;
import org.sakaiproject.memory.api.Cache;
import org.sakaiproject.memory.api.CacheRefresher;
import org.sakaiproject.memory.api.MemoryService;
import org.sakaiproject.lessonbuildertool.SimplePageItem;
import org.sakaiproject.lessonbuildertool.SimplePage;
import org.sakaiproject.lessonbuildertool.model.SimplePageToolDao;
import org.sakaiproject.lessonbuildertool.SimpleStudentPage;
import org.sakaiproject.lessonbuildertool.tool.beans.SimplePageBean;
import uk.org.ponder.messageutil.MessageLocator;
import org.sakaiproject.tool.api.ToolManager;
import org.sakaiproject.tool.api.SessionManager;
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.content.api.ContentHostingService;
import org.sakaiproject.memory.api.MemoryService;
// This class is used to check whether a page or item should be
// accessible for the user. It checks all conditions. SimplePageBean
// has individual checks for them, but this puts them all together,
// for use in services where individual checks aren't needed.
// In general this will be used by services such as /access or /direct
public class LessonsAccess {
private Log log = LogFactory.getLog(LessonsAccess.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 SimplePageToolDao dao;
private AuthzGroupService authzGroupService;
private SecurityService securityService;
private MemoryService memoryService;
public void init() {
cache = memoryService
.newCache("org.sakaiproject.lessonbuildertool.service.LessonsAccess.cache");
log.info("init()");
}
public void destroy() {
log.info("destroy()");
cache.destroy();
cache = null;
}
// 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 getItemPaths(item, [])
null means no constraints
[], i.e. empty set, means impossible
otherwise return a list of paths to get to it. Only one needs to work
pageId == null if path has no prerequisites
*/
public class Path implements Cloneable{
Long itemId;
boolean topLevel;
Set<String>groups;
public Path clone() {
Path ret = new Path();
ret.itemId = this.itemId;
ret.topLevel = this.topLevel;
ret.groups = this.groups;
return ret;
}
}
// the code for access is broken into 3 parts:
//
// 1) getPagePaths returns accessibility data that is the same for all users.
// that means that we can reasonably cache it, and get a good performance improvement.
//
// 2) isPageAccessible interprets the results of getPathPaths for a given user.
//
// 3) isItemAccessible checks that the containing page is accessible, and then
// calls the normal available and visible tests from SimplePageBean. In a lot of
// code those tests will be used directly, so this class will be used only
// for isPageAccessible. But for the /access and /direct handlers, this is
// the right way to check items.
Set<Path> getPagePaths (long pageId) {
return getPagePaths(pageId, new HashSet<Long>());
}
Set<Path> getPagePaths(long pageId, Set<Long>seen) {
/// System.out.println("page " + pageId + " getgroups");
// if pageid is 0 this is a top level page. No further constraints
Set<Path> ret = new HashSet<Path>();
if (pageId == 0) {
Path path = new Path();
path.groups = null;
path.topLevel = false;
path.itemId = null;
ret.add(path);
return ret;
}
SimplePage page = dao.getPage(pageId);
if (page == null) // should be impossible
return ret;
if (page.isHidden() || (page.getReleaseDate() != null && page.getReleaseDate().after(new Date()))) {
// not released. Say inaccessible. The assumption is that this is being used only
// for students. Obviously the instructor can bypass release control.
return ret;
}
// List of items with this page on it
List<SimplePageItem> items = null;
// if it's a student page, have to handle specially. Find item that has the student content section
if (page.getOwner() != null) {
SimpleStudentPage student = dao.findStudentPage(page.getTopParent());
SimplePageItem item = dao.findItem(student.getItemId());
items = new ArrayList<SimplePageItem>();
items.add(item);
} else
items = dao.findPageItemsBySakaiId(Long.toString(pageId));
seen.add(pageId);
for (SimplePageItem item: items) {
// union all their groups
String itemGroupString = item.getGroups();
Set<String>itemGroups = null;
if (itemGroupString != null && itemGroupString.length() > 0)
itemGroups = new HashSet<String>(Arrays.asList(itemGroupString.split(",")));
if (item.isPrerequisite()) {
// we don't do a recursive call for groups. because of the restriction on prerequisites,
// we will only allow access to this page if there is a log entry. But in that case
// we don't need further tests because the user had at some point gotten to the containing
// page. Note that a log entry will be created when the containing page is shown, as long
// as the page is marked has having prerequisites and they are satisfied. So log entry
// is a good test for prerequisites, and a lot less work than the real test.
Path path = new Path();
path.itemId = item.getId();
path.topLevel = (item.getPageId() == 0);
path.groups = itemGroups;
// TODO: check for duplicates
ret.add(path);
} else {
Set<Path> paths = getPagePaths(item.getPageId(), seen);
for (Path path: paths) {
// the values will end up cached. That means we can't
// modify the values in place, but have to copy them first
path = path.clone();
if (path.groups == null)
path.groups = itemGroups;
else if (itemGroups == null)
; // use path.groups as is
else {
// note: we can't modify that set in path.groups, because it coudl be shared
// with other copies of this Path
itemGroups.retainAll(path.groups); // intersect
path.groups = itemGroups;
}
ret.add(path);
}
}
}
seen.remove(pageId);
return ret;
}
// in testing this code, not that ispageaccessible will sometimes return true
// when testing isitemaccessible for an item pointing to the page will not.
// there are two reasons:
// * ispageaccessible will check all items that point to the page. Isitemaccessible
// will just check one of them.
// * for a page that is release controlled, ispageaccessible will check whether
// there is a log entry for that page. Isitemaccessible will check the actual
// condition. This is because doing the full check for every item in multiple paths
// is too expensive. The test in ispageaccessible allows any access that will happen
// in practice
// note that this doesn't check site permissions. lb.write won't need this
// check at all. lb.read needs to be true or this test is irrelevant.
// I assume this method will mostly be used by code that wants to do item
// checks on its own, so most likely it's already doign the permissions checks
public boolean isPageAccessible(long pageId, String siteId, String currentUserId, SimplePageBean simplePageBean) {
Set<Path> paths = getPagePaths(pageId);
for (Path path: paths) {
if (path.itemId != null) {
// page needs to be marked available. When the containing page
// finds that is accessible a dummy entry is created. So existence
// of an entry, dummy or real means it is accessible.
if (path.topLevel) {
// top level item. special code that checks tools
// if no prerequisites, top level items are OK
// groups will be checked below, though in fact top level items
// can't be restricted by group
SimplePageItem item = dao.findItem(path.itemId);
if (item.isPrerequisite()){
SimplePage currentPage = null;
if (simplePageBean == null) {
String pageString = item.getSakaiId();
long pageNum = 0;
try {
pageNum = Long.parseLong(pageString, 10);
} catch (Exception e) {
continue;
}
currentPage = dao.getPage(pageNum);
if (currentPage == null)
continue;
}
simplePageBean = makeSimplePageBean(simplePageBean, siteId, currentPage);
List<SimplePageItem> items = dao.findItemsInSite(siteId);
// sorted by SQL
boolean ok = true;
for (SimplePageItem i : items) {
if (i.getId() == path.itemId)
break; //ok
if (i.isRequired() && !simplePageBean.isItemComplete(i) && simplePageBean.isItemVisible(i)) {
ok = false;
break;
}
}
if (!ok)
continue;
}
} else if (dao.getLogEntry(currentUserId, path.itemId, -1L) == null)
continue;
}
if (path.groups == null)
return true;
else {
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, currentUserId);
if (matched.size() > 0)
return true;
}
}
return false;
}
public SimplePageBean makeSimplePageBean(SimplePageBean simplePageBean, String siteId, SimplePage currentPage) {
if (simplePageBean == null) {
simplePageBean = new SimplePageBean();
simplePageBean.setMessageLocator(messageLocator);
simplePageBean.setToolManager(toolManager);
simplePageBean.setSecurityService(securityService);
simplePageBean.setSessionManager(sessionManager);
simplePageBean.setSiteService(siteService);
simplePageBean.setContentHostingService(contentHostingService);
simplePageBean.setSimplePageToolDao(dao);
simplePageBean.setForumEntity(forumEntity);
simplePageBean.setQuizEntity(quizEntity);
simplePageBean.setAssignmentEntity(assignmentEntity);
simplePageBean.setBltiEntity(bltiEntity);
simplePageBean.setGradebookIfc(gradebookIfc);
simplePageBean.setMemoryService(memoryService);
simplePageBean.setCurrentSiteId(siteId);
simplePageBean.setCurrentPage(currentPage);
simplePageBean.setCurrentPageId(currentPage.getPageId());
simplePageBean.init();
}
return simplePageBean;
}
// currently does not check site permissions
// if you need to evaluate multiple items on a page, please use of create a
// simplePageBean and pass it. THat will let us cache a bunch of data between
// the calls, and save a lot of database queries
public boolean isItemAccessible(long itemId, String siteId, String currentUserId, SimplePageBean simplePageBean) {
SimplePageItem item = dao.findItem(itemId);
if (item == null) {
return false;
}
// top-level pseudo-item is special, as there is no containing page
// just test the page it points to
if (item.getPageId() == 0L && item.getType() == SimplePageItem.PAGE) {
String pageString = item.getSakaiId();
long pageNum = 0;
try {
pageNum = Long.parseLong(pageString, 10);
} catch (Exception e) {
return false;
}
return isPageAccessible(pageNum, siteId, currentUserId, simplePageBean);
}
SimplePage currentPage = dao.getPage(item.getPageId());
if (currentPage == null) {
return false;
}
simplePageBean = makeSimplePageBean(simplePageBean, siteId, currentPage);
// containing page must be accessible.
if (!isPageAccessible(currentPage.getPageId(), siteId, currentUserId, simplePageBean)) {
return false;
}
if (!simplePageBean.isItemAvailable(item, item.getPageId())) {
return false;
}
try {
if (!simplePageBean.isItemVisible(item, null, false)) {
return false;
}
} catch (Exception e) {
return false;
}
return true;
}
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 MessageLocator messageLocator;
public void setMessageLocator(MessageLocator s) {
messageLocator = s;
}
private ToolManager toolManager;
public void setToolManager(ToolManager s) {
toolManager = s;
}
SessionManager sessionManager = null;
public void setSessionManager(SessionManager s) {
sessionManager = s;
}
private SiteService siteService;
public void setSiteService(SiteService s) {
siteService = s;
}
ContentHostingService contentHostingService = null;
public void setContentHostingService(ContentHostingService s) {
contentHostingService = s;
}
LessonEntity forumEntity = null;
public void setForumEntity(Object e) {
forumEntity = (LessonEntity) e;
}
LessonEntity quizEntity = null;
public void setQuizEntity(Object e) {
quizEntity = (LessonEntity) e;
}
LessonEntity assignmentEntity = null;
public void setAssignmentEntity(Object e) {
assignmentEntity = (LessonEntity) e;
}
LessonEntity bltiEntity = null;
public void setBltiEntity(Object e) {
bltiEntity = (LessonEntity)e;
}
private GradebookIfc gradebookIfc = null;
public void setGradebookIfc(GradebookIfc g) {
gradebookIfc = g;
}
}