/**********************************************************************************
* $URL: $
* $Id: $
***********************************************************************************
AssessmentService
Assessment
AssessmentDates
*
* Author: Charles Hedrick, hedrick@rutgers.edu
*
* Copyright (c) 2010 Rutgers, the State University of New Jersey
*
* 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.opensource.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.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Comparator;
import java.util.Date;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.lessonbuildertool.service.LessonSubmission;
import org.sakaiproject.lessonbuildertool.tool.beans.SimplePageBean;
import org.sakaiproject.lessonbuildertool.tool.beans.SimplePageBean.UrlItem;
import org.etudes.mneme.api.AssessmentService;
import org.etudes.mneme.api.Assessment;
import org.etudes.mneme.api.AssessmentDates;
import org.etudes.mneme.api.SubmissionService;
import org.sakaiproject.site.api.Group;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.cover.SiteService;
import org.sakaiproject.tool.api.Session;
import org.sakaiproject.tool.cover.ToolManager;
import org.sakaiproject.tool.cover.SessionManager;
import org.sakaiproject.site.api.ToolConfiguration;
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.component.cover.ServerConfigurationService;
import org.w3c.dom.Document;
import org.sakaiproject.memory.api.Cache;
import org.sakaiproject.memory.api.CacheRefresher;
import org.sakaiproject.memory.api.MemoryService;
import org.etudes.mneme.api.ImportQtiService;
import uk.org.ponder.messageutil.MessageLocator;
/**
* Interface to Mneme
*
* @author Charles Hedrick <hedrick@rutgers.edu>
*
*/
public class MnemeEntity implements LessonEntity, QuizEntity {
private static Log log = LogFactory.getLog(MnemeEntity.class);
private static Cache assessmentCache = null;
protected static final int DEFAULT_EXPIRATION = 10 * 60;
static AssessmentService assessmentService = (AssessmentService)
ComponentManager.get("org.etudes.mneme.api.AssessmentService");
static SubmissionService submissionService = (SubmissionService)
ComponentManager.get("org.etudes.mneme.api.SubmissionService");
private SimplePageBean simplePageBean;
public void setSimplePageBean(SimplePageBean simplePageBean) {
this.simplePageBean = simplePageBean;
}
private LessonEntity nextEntity = null;
public void setNextEntity(LessonEntity e) {
nextEntity = e;
}
public LessonEntity getNextEntity() {
return nextEntity;
}
// stick ourselves after the last required entity
public void setPrevEntity(LessonEntity e) {
e.setNextEntity(this);
}
static MemoryService memoryService = null;
public void setMemoryService(MemoryService m) {
memoryService = m;
}
static MessageLocator messageLocator = null;
public void setMessageLocator(MessageLocator m) {
messageLocator = m;
}
static ImportQtiService importQtiService = (ImportQtiService)
ComponentManager.get("org.etudes.mneme.api.ImportQtiService");
public void init () {
assessmentCache = memoryService
.newCache("org.sakaiproject.lessonbuildertool.service.MnemeEntity.cache");
log.info("init()");
}
public void destroy()
{
assessmentCache.destroy();
assessmentCache = null;
log.info("destroy()");
}
// to create bean. the bean is used only to call the pseudo-static
// methods such as getEntitiesInSite. So type, id, etc are left uninitialized
protected MnemeEntity() {
}
protected MnemeEntity(int type, String id, int level) {
this.type = type;
this.id = id;
this.level = level;
}
public String getToolId() {
return "sakai.mneme";
}
// the underlying object, something Sakaiish
protected String id;
protected int type;
protected int level;
// not required fields. If we need to look up
// the actual objects, lets us cache them
private Assessment assessment;
private Assessment getAssessment(String id) {
Assessment ret = (Assessment)assessmentCache.get(id);
if (ret != null) {
// don't check for published. Faculty may temporarily unpublish
// to edit. We don't want it to disappear from students
if (ret.getArchived() || ret.getMint())
return null;
return ret;
}
if (assessmentService == null)
return null;
ret = assessmentService.getAssessment(id);
if (ret != null) {
// cache it if we find it, even if not acceptable
assessmentCache.put(id, ret, DEFAULT_EXPIRATION);
// don't check for published. Faculty may temporarily unpublish
// to edit. We don't want it to disappear from students
if (ret.getArchived() || ret.getMint())
return null;
}
return ret;
}
// type of the underlying object
public int getType() {
return type;
}
public int getTypeOfGrade() {
return 1;
}
public int getLevel() {
return level;
}
// hack for forums. not used for assessments, so always ok
public boolean isUsable() {
return true;
}
public String getReference() {
return "/" + MNEME + "/" + id;
}
public List<LessonEntity> getEntitiesInSite() {
return getEntitiesInSite(null);
}
// find topics in site, but organized by forum
public List<LessonEntity> getEntitiesInSite(SimplePageBean bean) {
Session ses = SessionManager.getCurrentSession();
List<LessonEntity> ret = new ArrayList<LessonEntity>();
if (assessmentService == null)
return ret;
List<Assessment> plist = assessmentService.getContextAssessments(ToolManager.getCurrentPlacement().getContext(), AssessmentService.AssessmentsSort.title_a, true);
// security. assume this is only used in places where it's OK, so skip security checks
for (Assessment assessment: plist) {
MnemeEntity entity = new MnemeEntity(TYPE_MNEME, assessment.getId(), 1);
entity.assessment = assessment;
ret.add(entity);
}
if (nextEntity != null)
ret.addAll(nextEntity.getEntitiesInSite(bean));
return ret;
}
public LessonEntity getEntity(String ref, SimplePageBean o) {
return getEntity(ref);
}
public LessonEntity getEntity(String ref) {
int i = ref.indexOf("/",1);
String typeString = ref.substring(1, i);
String id = ref.substring(i+1);
if (typeString.equals(MNEME)) {
return new MnemeEntity(TYPE_MNEME, id, 1);
} else if (nextEntity != null) {
return nextEntity.getEntity(ref);
} else
return null;
}
// properties of entities
public String getTitle() {
if (assessment == null)
assessment = getAssessment(id);
if (assessment == null)
return null;
return assessment.getTitle();
}
public String getUrl() {
Site site = null;
ToolConfiguration siteTool = null;
try {
site = SiteService.getSite(ToolManager.getCurrentPlacement().getContext());
siteTool = site.getToolForCommonId("sakai.mneme");
} catch (Exception e) {
return null;
}
if (siteTool == null)
return null;
return ServerConfigurationService.getToolUrl()+ "/" + siteTool.getId() + "/enter/" + id;
}
// I don't think they have this
public Date getDueDate() {
if (assessment == null)
assessment = getAssessment(id);
if (assessment == null)
return null;
return assessment.getDates().getDueDate();
}
// the following methods all take references. So they're in effect static.
// They ignore the entity from which they're called.
// The reason for not making them a normal method is that many of the
// implementations seem to let you set access control and find submissions
// from a reference, without needing the actual object. So doing it this
// way could save some database activity
// access control
public boolean addEntityControl(String siteId, String groupId) throws IOException {
return true;
}
public boolean removeEntityControl(String siteId, String groupId) throws IOException {
return true;
}
// submission
// do we need the data from submission?
public boolean needSubmission(){
return true;
}
public LessonSubmission getSubmission(String user) {
if (assessment == null)
assessment = getAssessment(id);
if (assessment == null) {
log.warn("can't find published " + id);
return null;
}
if (submissionService == null)
return null;
Session ses = SessionManager.getCurrentSession();
Double score = toDouble(submissionService.getSubmissionOfficialScore(assessment, ses.getUserId()));
if (score == null)
return null;
return new LessonSubmission(score);
}
public Double toDouble(Object f) {
if (f instanceof Double)
return (Double)f;
else if (f instanceof Float)
return ((Float)f).doubleValue();
else
return null;
}
// we can do this for real, but the API will cause us to get all the submissions in full, not just a count.
// I think it's cheaper to get the best assessment, since we don't actually care whether it's 1 or >= 1.
public int getSubmissionCount(String user) {
if (getSubmission(user) == null)
return 0;
else
return 1;
}
// URL to create a new item. Normally called from the generic entity, not a specific one
// can't be null
public List<UrlItem> createNewUrls(SimplePageBean bean) {
ArrayList<UrlItem> list = new ArrayList<UrlItem>();
String tool = bean.getCurrentTool("sakai.mneme");
if (tool != null) {
tool = ServerConfigurationService.getToolUrl()+ "/" + tool + "/assessments";
list.add(new UrlItem(tool, messageLocator.getMessage("simplepage.create_mneme")));
}
if (nextEntity != null)
list.addAll(nextEntity.createNewUrls(bean));
return list;
}
// URL to edit an existing entity.
// Can be null if we can't get one or it isn't needed
public String editItemUrl(SimplePageBean bean) {
String tool = bean.getCurrentTool("sakai.mneme");
if (tool == null)
return null;
return ServerConfigurationService.getToolUrl()+ "/" + tool + "/assessment_edit/" + id + "/1";
}
// for most entities editItem is enough, however tests allow separate editing of
// contents and settings. This will be null except in that situation
public String editItemSettingsUrl(SimplePageBean bean) {
String tool = bean.getCurrentTool("sakai.mneme");
if (tool == null)
return null;
return ServerConfigurationService.getToolUrl()+ "/" + tool + "/assessment_settings/" + id + "/1";
}
public String importObject(Document document, boolean isBank, String siteId, boolean hide) {
// ignore isbank because everything has to go into pool.
// they have no way to import an assessment
if (importQtiService == null)
return null;
try {
importQtiService.importPool(document, siteId);
} catch (Exception e) {
simplePageBean.setErrMessage("" + e);
}
return null; // for now. should be sakaiid
}
public boolean objectExists() {
if (assessment == null)
assessment = getAssessment(id);
return assessment != null;
}
public boolean notPublished(String ref) {
return false;
}
// return the list of groups if the item is only accessible to specific groups
// null if it's accessible to the whole site.
public Collection<String> getGroups(boolean nocache) {
return null;
}
// set the item to be accessible only to the specific groups.
// null to make it accessible to the whole site
public void setGroups(Collection<String> groups) {
}
// currently Mneme does not participate in the fixup. However I'm going to include
// the ID anyway, just in case it happens in the future. Doesn't currently support
// direct, so it's just a guess what prefix it might use
public String getObjectId(){
String title = getTitle();
if (title == null)
return null;
return "mneme/" + id + "/" + title;
}
public String findObject(String objectid, Map<String,String>objectMap, String siteid) {
if (!objectid.startsWith("mneme/")) {
if (nextEntity != null)
return nextEntity.findObject(objectid, objectMap, siteid);
return null;
}
if (assessmentService == null)
return null;
// isolate mneme/NNN from title
int i = objectid.indexOf("/", "mneme/".length());
if (i <= 0)
return null;
String realobjectid = objectid.substring(0, i);
// now see if it's in the map. not currently possible, but who knows
String newAssessment = objectMap.get(realobjectid);
if (newAssessment != null)
return "/" + newAssessment; // sakaiid is /mneme/ID
// Can't find the assessment in the map
// i is start of title
String title = objectid.substring(i+1);
// this would be more efficient if I did my own query, but prefer to use API where possible
List<Assessment> plist = assessmentService.getContextAssessments(ToolManager.getCurrentPlacement().getContext(), AssessmentService.AssessmentsSort.title_a, true);
// security. assume this is only used in places where it's OK, so skip security checks
for (Assessment assessment: plist) {
if (assessment.getTitle().equals(title))
return "/mneme/" + assessment.getId();
}
return null;
}
public String getSiteId() {
if (assessment == null)
assessment = getAssessment(id);
if (assessment == null)
return null;
return assessment.getContext();
}
}