/********************************************************************************** * $URL: $ * $Id: $ *********************************************************************************** * * 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.Arrays; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.HashSet; 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.component.cover.ServerConfigurationService; import org.sakaiproject.lessonbuildertool.service.LessonSubmission; import org.sakaiproject.lessonbuildertool.tool.beans.SimplePageBean; import org.sakaiproject.lessonbuildertool.tool.beans.SimplePageBean.UrlItem; import org.sakaiproject.tool.assessment.data.ifc.assessment.PublishedAssessmentIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.AssessmentIfc; import org.sakaiproject.tool.assessment.services.assessment.PublishedAssessmentService; import org.sakaiproject.tool.assessment.data.dao.assessment.PublishedMetaData; import org.sakaiproject.tool.assessment.services.assessment.AssessmentService; import org.sakaiproject.tool.assessment.facade.PublishedAssessmentFacade; import org.sakaiproject.tool.assessment.facade.AssessmentFacade; import org.sakaiproject.tool.assessment.facade.AssessmentFacadeQueries; import org.sakaiproject.tool.assessment.facade.AuthzQueriesFacadeAPI; import org.sakaiproject.tool.assessment.facade.PublishedAssessmentFacadeQueriesAPI; import org.sakaiproject.tool.assessment.data.dao.authz.AuthorizationData; import org.sakaiproject.tool.assessment.data.ifc.assessment.EvaluationModelIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.AssessmentAccessControlIfc; import org.sakaiproject.tool.assessment.data.dao.assessment.PublishedAssessmentData; import org.sakaiproject.tool.assessment.data.dao.assessment.AssessmentData; import org.sakaiproject.tool.assessment.data.dao.assessment.AssessmentAccessControl; import org.sakaiproject.tool.assessment.data.dao.grading.AssessmentGradingData; import org.sakaiproject.tool.assessment.services.GradingService; import org.sakaiproject.tool.assessment.services.PersistenceService; import org.w3c.dom.Document; import org.sakaiproject.tool.assessment.services.qti.QTIService; import org.sakaiproject.tool.assessment.qti.constants.QTIVersion; 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.memory.api.Cache; import org.sakaiproject.memory.api.CacheRefresher; import org.sakaiproject.memory.api.MemoryService; import uk.org.ponder.messageutil.MessageLocator; import org.sakaiproject.lessonbuildertool.SimplePageItem; import org.sakaiproject.lessonbuildertool.model.SimplePageToolDao; import org.sakaiproject.db.cover.SqlService; import org.sakaiproject.db.api.SqlReader; import java.sql.Connection; import java.sql.ResultSet; /** * Interface to Message Forums, the forum that comes with Sakai * * @author Charles Hedrick <hedrick@rutgers.edu> * */ // NOTE: almost no other class should import this. We want to be able // to support both forums and jforum. So typically there will be a // forumEntity, but it's injected, and it can be either forum and jforum. // Hence it has to be declared LessonEntity. That leads to a lot of // declarations like LessonEntity forumEntity. In this case forumEntity // means either a ForumEntity or a JForumEntity. We can't just call the // variables lessonEntity because the same module will probably have an // injected class to handle tests and quizes as well. That will eventually // be converted to be a LessonEntity. public class SamigoEntity implements LessonEntity, QuizEntity { private static Log log = LogFactory.getLog(SamigoEntity.class); private static Cache assessmentCache = null; protected static final int DEFAULT_EXPIRATION = 10 * 60; private static boolean samigo_linked = false; PublishedAssessmentService pService = new PublishedAssessmentService(); AssessmentService assessmentService = new AssessmentService(); private SimplePageToolDao simplePageToolDao; public void setSimplePageToolDao(Object dao) { simplePageToolDao = (SimplePageToolDao) dao; } private SimplePageBean simplePageBean; public void setSimplePageBean(SimplePageBean simplePageBean) { this.simplePageBean = simplePageBean; } static PublishedAssessmentFacadeQueriesAPI publishedAssessmentFacadeQueries; public void setPublishedAssessmentFacadeQueries( PublishedAssessmentFacadeQueriesAPI p) { this.publishedAssessmentFacadeQueries = p; } private LessonEntity nextEntity = null; public void setNextEntity(LessonEntity e) { nextEntity = e; } public LessonEntity getNextEntity() { return nextEntity; } static MemoryService memoryService = null; public void setMemoryService(MemoryService m) { memoryService = m; } static MessageLocator messageLocator = null; public void setMessageLocator(MessageLocator m) { messageLocator = m; } public void init () { assessmentCache = memoryService .newCache("org.sakaiproject.lessonbuildertool.service.SamigoEntity.cache"); String sakaiVersion = ServerConfigurationService.getString("version.sakai", "2.6"); // the samigo distributed with 2.8 final has the link boolean defaultEditLink = false; int cle = 2; int major = 6; int minor = 0; if (sakaiVersion != null) { String []parts = sakaiVersion.split("\\."); if (parts.length >= 1) { try { cle = Integer.parseInt(parts[0]); } catch (Exception e) { }; } if (parts.length >= 2) { try { String[] s = parts[1].split("\\D"); major = Integer.parseInt(s[0]); } catch (Exception e) { }; } // may be something like 2.8.1-foo, so must terminate on non-digit if (parts.length >= 3) { try { String[] s = parts[2].split("\\D"); minor = Integer.parseInt(s[0]); } catch (Exception e) { }; } // samigo starting with 2.8.1 has the edit link if (cle > 2 || (cle == 2 && (major == 8 && minor > 0 || major > 8))) defaultEditLink = true; } System.out.println("SamigoEntity thinks this is Sakai verison " + cle + "." + major + "." + minor + ", defaulting Samigo edit link to " + defaultEditLink); samigo_linked = ServerConfigurationService.getBoolean("lessonbuilder.samigo.editlink", defaultEditLink); 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 SamigoEntity() { } protected SamigoEntity(int type, Long id, int level) { this.type = type; this.id = id; this.level = level; } public String getToolId() { return "sakai.samigo"; } // the underlying object, something Sakaiish protected Long id; protected int type; protected int level; // not required fields. If we need to look up // the actual objects, lets us cache them protected PublishedAssessmentData assessment; public PublishedAssessmentData getPublishedAssessment(Long publishedId) { return getPublishedAssessment(publishedId, false); } public PublishedAssessmentData getPublishedAssessment(Long publishedId, boolean nocache) { PublishedAssessmentData ret = (PublishedAssessmentData)assessmentCache.get(publishedId); if (!nocache && ret != null) { return ret; } try { ret = publishedAssessmentFacadeQueries.loadPublishedAssessment(publishedId); // this will ignore retracted. I think that's right. Students // we show dead and inactive, just not deleted if (ret.getStatus().equals(PublishedAssessmentFacade.DEAD_STATUS)) { return null; } } catch (Exception e) { return null; } if (ret != null) { ret.setComments(null); assessmentCache.put(publishedId, ret, DEFAULT_EXPIRATION); } 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 "/" + SAM_PUB + "/" + 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(); ArrayList<PublishedAssessmentFacade> plist = pService.getBasicInfoOfAllPublishedAssessments2("title", true, ToolManager.getCurrentPlacement().getContext()); List<LessonEntity> ret = new ArrayList<LessonEntity>(); // security. assume this is only used in places where it's OK, so skip security checks for (PublishedAssessmentFacade assessment: plist) { SamigoEntity entity = null; if (assessment.getStatus().equals(AssessmentIfc.ACTIVE_STATUS)) { entity = new SamigoEntity(TYPE_SAMIGO, assessment.getPublishedAssessmentId(), 1); entity.assessment = (PublishedAssessmentData)assessment.getData(); ret.add(entity); } if (false) { // testing System.out.println(entity.getGroups(true)); List<String> oldGroups = entity.getGroups(true); //5c51c1fb-bf48-475f-99a6-a303f5ad9520 //d579a252-204e-46cd-9720-7eca7bd47630 entity.setGroups(null); System.out.println("null " + entity.getGroups(true)); entity.setGroups(null); System.out.println("null " + entity.getGroups(true)); entity.setGroups(Arrays.asList("5c51c1fb-bf48-475f-99a6-a303f5ad9520")); System.out.println("5c51 " + entity.getGroups(true)); entity.setGroups(Arrays.asList("5c51c1fb-bf48-475f-99a6-a303f5ad9520","d579a252-204e-46cd-9720-7eca7bd47630")); System.out.println("5c51,d579 " + entity.getGroups(true)); entity.setGroups(null); System.out.println("null " + entity.getGroups(true)); entity.setGroups(oldGroups); System.out.println(oldGroups + " " + entity.getGroups(true)); } } if (nextEntity != null) ret.addAll(nextEntity.getEntitiesInSite(bean)); return ret; } public LessonEntity getEntity(String ref) { return getEntity(ref, null); } public LessonEntity getEntity(String ref, SimplePageBean o) { // if the site was copied, all sakaiids for tests are set to something like /sam_core/NNN // the problem is that published asessments aren't copied. So all we can do is poitn to // the core assessment. Of course you can't really take that, so we try to find a published // assessment based on that core. If we find one, we fix up the sakaiids, and we're ok. if (o != null && ref.startsWith("/sam_core/")) { Object fields[] = new Object[1]; fields[0] = new Long(ref.substring("/sam_core/".length())); // assessmentid is indexed, so this is a pretty lightweight query List <String> publishedIds = SqlService.dbRead("select ID from SAM_PUBLISHEDASSESSMENT_T where ASSESSMENTID=?", fields, null); if (publishedIds != null && publishedIds.size() > 0) { // found it, update all sakaiids to use the published assessment String newid = publishedIds.get(0); List <SimplePageItem> items = simplePageToolDao.findItemsBySakaiId(ref); ref = "/sam_pub/" + newid; for (SimplePageItem item: items) { item.setSakaiId(ref); simplePageToolDao.quickUpdate(item); o.checkControlGroup(item, item.isPrerequisite()); } } else return null; // ref has now been updated, so the rest of the code should work as is } int i = ref.indexOf("/",1); if (i < 0) { // old format, just the number try { return new SamigoEntity(TYPE_SAMIGO, Long.valueOf(ref), 1); } catch (Exception ignore) { return null; } } String typeString = ref.substring(1, i); String idString = ref.substring(i+1); Long id = 0L; try { id = Long.parseLong(idString); } catch (Exception ignore) { return null; } if (typeString.equals(SAM_PUB)) { return new SamigoEntity(TYPE_SAMIGO, id, 1); } else if (nextEntity != null) { return nextEntity.getEntity(ref); } else return null; } // properties of entities public String getTitle() { if (assessment == null) assessment = getPublishedAssessment(id); if (assessment == null) return null; return assessment.getTitle(); } public String getAssessmentAlias(Long publishedId) { try { PublishedAssessmentData a = getPublishedAssessment(publishedId); if (a == null) return null; else return a.getAssessmentMetaDataByLabel("ALIAS"); } catch (Exception ex) { System.out.println("exception " + ex); return null; } } public String getUrl() { return "/samigo-app/servlet/Login?id=" + getAssessmentAlias(id); } // I don't think they have this public Date getDueDate() { if (assessment == null) assessment = getPublishedAssessment(id); if (assessment == null) return null; return assessment.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 { // I don't want to do a full load of the facade most of the time. So we use // PublishedAssessmentData normally. Unfortunately here we need it PublishedAssessmentFacade assessment = null; AssessmentAccessControlIfc control = null; try { assessment = pService.getPublishedAssessment(Long.toString(id)); control = assessment.getAssessmentAccessControl(); } catch (Exception e) { log.warn("can't find published " + id, e); return false; } AuthzQueriesFacadeAPI authz = PersistenceService.getInstance().getAuthzQueriesFacade(); if (authz == null) { log.warn("Null Authorization"); return false; } if (!control.getReleaseTo().equals(AssessmentAccessControlIfc.RELEASE_TO_SELECTED_GROUPS)) { control.setReleaseTo(AssessmentAccessControlIfc.RELEASE_TO_SELECTED_GROUPS); pService.saveAssessment(assessment); String qualifierIdString = assessment.getPublishedAssessmentId().toString(); // the original one lists the site. once we set release to groups, it will try to look // up the site id as a group id. very bad, so remove all existing ones. authz.removeAuthorizationByQualifierAndFunction(qualifierIdString, "TAKE_PUBLISHED_ASSESSMENT"); // and add our group authz.createAuthorization(groupId, "TAKE_PUBLISHED_ASSESSMENT", Long.toString(id)); } else { // already release to groups. see if we need to add our group List<AuthorizationData> authorizations = authz.getAuthorizationByFunctionAndQualifier("TAKE_PUBLISHED_ASSESSMENT", Long.toString(id)); boolean found = false; for (AuthorizationData ad : authorizations) { if (ad.getAgentIdString().equals(groupId)) { found = true; break; } } // if not, add it; can't add it otherwise or we get duplicates if (!found) { authz.createAuthorization(groupId, "TAKE_PUBLISHED_ASSESSMENT", Long.toString(id)); } } return true; } public boolean removeEntityControl(String siteId, String groupId) throws IOException { // I don't want to do a full load of the facade most of the time. So we use // PublishedAssessmentData normally. Unfortunately here we need it PublishedAssessmentFacade assessment = null; AssessmentAccessControlIfc control = null; try { assessment = pService.getPublishedAssessment(Long.toString(id)); control = assessment.getAssessmentAccessControl(); } catch (Exception e) { log.warn("can't find published " + id, e); return false; } AuthzQueriesFacadeAPI authz = PersistenceService.getInstance().getAuthzQueriesFacade(); if (!control.getReleaseTo().equals(AssessmentAccessControlIfc.RELEASE_TO_SELECTED_GROUPS)) { // not release to groups, nothing to do return true; } else { // what do we do if it was originally released to groups, and then we added ours? I // guess jsut remove ours? List<AuthorizationData> authorizations = authz.getAuthorizationByFunctionAndQualifier("TAKE_PUBLISHED_ASSESSMENT", Long.toString(id)); boolean foundother = false; for (AuthorizationData ad : authorizations) { if (ad.getAgentIdString().equals(groupId)) {} else { foundother = true; } } if (foundother) { // just remove our group authz.removeAuthorizationByAgentQualifierAndFunction(groupId, Long.toString(id), "TAKE_PUBLISHED_ASSESSMENT"); } else { // otherwise remove all groups authz.removeAuthorizationByQualifierAndFunction(Long.toString(id), "TAKE_PUBLISHED_ASSESSMENT"); } Site site = null; try { site = SiteService.getSite(siteId); } catch (Exception e) { return false; } // put back the site authz.createAuthorization(siteId, "TAKE_PUBLISHED_ASSESSMENT", Long.toString(id)); // and put back the access control control.setReleaseTo(site.getTitle()); // what if it's too long? // and save the updated info pService.saveAssessment(assessment); } return true; } // submission // do we need the data from submission? public boolean needSubmission(){ return true; } public LessonSubmission getSubmission(String user) { if (assessment == null) assessment = getPublishedAssessment(id); if (assessment == null) { log.warn("can't find published " + id); return null; } GradingService gradingService = new GradingService(); Session ses = SessionManager.getCurrentSession(); AssessmentGradingData grading = null; if (assessment.getEvaluationModel().getScoringType() == EvaluationModelIfc.LAST_SCORE) { grading = gradingService.getLastSubmittedAssessmentGradingByAgentId(Long.toString(id), ses.getUserId(), null); } else { // the declared return type changed from AssessmentGradingIfc to Data. But the actual // underlying object is Data. In the old code Data implemented Ifc, but that no longer // seems to be true. I believe this cast will work either way. grading = (AssessmentGradingData)gradingService.getHighestSubmittedAssessmentGrading(Long.toString(id), ses.getUserId()); } if (grading == null) return null; return new LessonSubmission(toDouble(grading.getFinalScore())); } 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.samigo"); if (tool != null) { tool = ServerConfigurationService.getToolUrl() + "/" + tool + "/jsf/index/mainIndex"; list.add(new UrlItem(tool, messageLocator.getMessage("simplepage.create_samigo"))); } 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.samigo"); if (tool == null) return null; if (false) { // code to verify that exportObject actually works if (assessment == null) assessment = getPublishedAssessment(id); String aid = assessment.getAssessmentId().toString(); Document doc = exportObject(aid); System.out.println("foo " + doc.getElementsByTagName("questestinterop")); } if (samigo_linked) return ServerConfigurationService.getToolUrl() + "/" + tool + "/jsf/author/editLink?publishedAssessmentId=" + id; else return ServerConfigurationService.getToolUrl() + "/" + tool + "/jsf/index/mainIndex"; } // 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.samigo"); if (tool == null) return null; if (samigo_linked) return ServerConfigurationService.getToolUrl() + "/" + tool + "/jsf/author/editLink?publishedAssessmentId=" + id + "&settings=true"; else return ServerConfigurationService.getToolUrl() + "/" + tool + "/jsf/index/mainIndex"; } // export an assessment as an XML document. This is in Samigo's version of QTI public Document exportObject(String assessmentId) { try { QTIService qtiService = new QTIService(); return qtiService.getExportedAssessment(assessmentId, QTIVersion.VERSION_1_2); } catch (Exception e) { System.out.println("exception in exportobject " + e); return null; } } public String importObject(Document document, boolean isBank, String siteId, boolean hide) { QTIService qtiService = new QTIService(); AssessmentFacade assessment = null; PublishedAssessmentFacade publishedAssessment = null; if (isBank) { qtiService.createImportedQuestionPool(document, QTIVersion.VERSION_1_2); return null; } else { assessment = qtiService.createImportedAssessment(document, QTIVersion.VERSION_1_2); try { // can't find a way to do this in the metadata, and in fact the real import // code does hack on this stuff AssessmentAccessControl control = new AssessmentAccessControl(); control.setAssessmentBase(assessment.getData()); assessment.setSecuredIPAddressSet(new HashSet()); assessment.setAssessmentAttachmentSet(new HashSet()); assessmentService.saveAssessment(assessment); // what we just did gives us empty access control. we need the real thing // this code is based on setgroups. It's kind of reverse engineered AssessmentAccessControlIfc controlIfc = null; Long id = assessment.getAssessmentId(); try { assessment = assessmentService.getAssessment(Long.toString(id)); controlIfc = assessment.getAssessmentAccessControl(); } catch (Exception e) { log.warn("can't find assessment we just loaded " + id, e); return null; } // set proper access control AuthzQueriesFacadeAPI authz = PersistenceService.getInstance().getAuthzQueriesFacade(); if (authz == null) { log.warn("Null Authorization"); return null; } authz.removeAuthorizationByQualifierAndFunction(Long.toString(id), "TAKE_PUBLISHED_ASSESSMENT"); Site site; try { site = SiteService.getSite(siteId); } catch (Exception e) { return null; } // put back the site // authz.createAuthorization(siteId, "TAKE_PUBLISHED_ASSESSMENT", Long.toString(id)); // and put back the access control controlIfc.setReleaseTo(site.getTitle()); // what if it's too long? // and save the updated info assessmentService.saveAssessment(assessment); // get it again try { assessment = assessmentService.getAssessment(Long.toString(id)); controlIfc = assessment.getAssessmentAccessControl(); } catch (Exception e) { log.warn("can't find assessment we just loaded " + id, e); return null; } if (!hide) { publishedAssessment = pService.publishAssessment(assessment); String alias = SessionManager.getCurrentSessionUserId() + (new Date()).getTime(); PublishedMetaData meta = new PublishedMetaData(publishedAssessment.getData(), "ALIAS", alias); pService.saveOrUpdateMetaData(meta); } } catch (Exception e) { log.warn("can't publish assessment after import " + e); e.printStackTrace(); } if (publishedAssessment != null) { return "/" + SAM_PUB + "/" + publishedAssessment.getPublishedAssessmentId(); } else return null; } } public boolean objectExists() { if (assessment == null) assessment = getPublishedAssessment(id); return assessment != null; } public boolean notPublished(String ref) { System.out.println("notpub " + ref); if (ref.startsWith("/sam_core/")) return true; else 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. Update the data in the cache // use the comments field, since there's no place to put a list and we don't use // that field public List<String> getGroups(boolean nocache) { if (nocache) assessment = getPublishedAssessment(id, true); else if (assessment == null) assessment = getPublishedAssessment(id); if (assessment == null) return null; // our model doens't include anonymous. Treat as no groups String releaseTo = assessment.getAssessmentAccessControl().getReleaseTo(); if (releaseTo != null && releaseTo.indexOf("Anonymous Users")> -1) return null; // cached value? String groupString = assessment.getComments(); if (groupString != null) { if (groupString.equals("")) // release to site return null; else return Arrays.asList(groupString.split(",")); } // no, get the value String siteId = ToolManager.getCurrentPlacement().getContext(); List<String> groups = publishedAssessmentFacadeQueries.getReleaseToGroupIdsForPublishedAssessment(assessment.getPublishedAssessmentId()+""); if (groups == null) return null; // if it's released to site we get a list with the site id in it if (groups.size() == 1 && groups.get(0).equals(siteId)) groupString = ""; // released to site else for (String group:groups) { if (groupString == null) groupString = group; else groupString = groupString + "," + group; } // cache it assessment.setComments(groupString); // return it if (groupString == null || groupString.equals("")) return null; else return groups; } // 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) { if (groups != null && groups.size() == 0) groups = null; // kill cached value. not perfect, as other systems // will still have the old value if (assessment != null) assessment.setComments(null); PublishedAssessmentData cached = (PublishedAssessmentData)assessmentCache.get(id); if (cached != null) cached.setComments(null); String siteId = ToolManager.getCurrentPlacement().getContext(); // I don't want to do a full load of the facade most of the time. So we use // PublishedAssessmentData normally. Unfortunately here we need it PublishedAssessmentFacade assessment = null; AssessmentAccessControlIfc control = null; try { assessment = pService.getPublishedAssessment(Long.toString(id)); control = assessment.getAssessmentAccessControl(); } catch (Exception e) { log.warn("can't find published " + id, e); return; } // no groups for anonymous assessments String releaseTo = control.getReleaseTo(); if (releaseTo != null && releaseTo.indexOf("Anonymous Users")> -1) return; AuthzQueriesFacadeAPI authz = PersistenceService.getInstance().getAuthzQueriesFacade(); if (authz == null) { log.warn("Null Authorization"); return; } // // got the info, now have the 4 possibilities // 1. asked for release to site, already is. if (groups == null && !control.getReleaseTo().equals(AssessmentAccessControlIfc.RELEASE_TO_SELECTED_GROUPS)) return; // 2. asked for release to site but it's now release to groups if (groups == null) { // remove all groups authz.removeAuthorizationByQualifierAndFunction(Long.toString(id), "TAKE_PUBLISHED_ASSESSMENT"); Site site; try { site = SiteService.getSite(siteId); } catch (Exception e) { return; } // put back the site authz.createAuthorization(siteId, "TAKE_PUBLISHED_ASSESSMENT", Long.toString(id)); // and put back the access control control.setReleaseTo(site.getTitle()); // what if it's too long? // and save the updated info pService.saveAssessment(assessment); return; } // 3 and 4 asked for release to groups // 3. it's currently release to site, update to groups if (!control.getReleaseTo().equals(AssessmentAccessControlIfc.RELEASE_TO_SELECTED_GROUPS)) { control.setReleaseTo(AssessmentAccessControlIfc.RELEASE_TO_SELECTED_GROUPS); pService.saveAssessment(assessment); } // 3 and 4. recreate the group list String qualifierIdString = assessment.getPublishedAssessmentId().toString(); // remove existing authz.removeAuthorizationByQualifierAndFunction(qualifierIdString, "TAKE_PUBLISHED_ASSESSMENT"); // and add new list for (String groupId: groups) { authz.createAuthorization(groupId, "TAKE_PUBLISHED_ASSESSMENT", Long.toString(id)); } } // this is what goes into the XML file. Samigo doesn't support RefMigrator, so the only way // we can connect tests in the new site with the old one is by title. Thus what we save is // use sam_core/TITLE // this is the title of the core assessment, since that's what gets copied. public String getObjectId(){ if (assessment == null) assessment = getPublishedAssessment(id); if (assessment == null) return null; Long coreId = assessment.getAssessmentId(); AssessmentFacade facade = assessmentService.getAssessment(coreId.toString()); if (facade == null) return null; String title = facade.getTitle(); return "sam_core/" + title; } // normally this will look up the object ID and find the corresponding sakaiid in the // new site. Unfortunately, the assessment hasn't been published, so the best we // can do is return the corresponding core assessment. When we try to refer to it, // getEntity will see if it's been published yet. // returns sam_core/NNNN public String findObject(String objectid, Map<String,String>objectMap, String siteid) { if (!objectid.startsWith("sam_core/")) { if (nextEntity != null) { return nextEntity.findObject(objectid, objectMap, siteid); } return null; } String title = objectid.substring("sam_core/".length()); // this is an expensive query, but this is called pretty rarely, so it's probably better to do this than // got to the database ourselves. We'd be a lot better with a getBasicInfo version List<AssessmentData> list = assessmentService.getAllActiveAssessmentsbyAgent(siteid); for (AssessmentData data: list) { if (data.getTitle().equals(title)) { return "/sam_core/" + data.getAssessmentBaseId(); } } return null; } public String getSiteId() { return publishedAssessmentFacadeQueries.getPublishedAssessmentSiteId(Long.toString(id)); } }