/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/sam/trunk/samigo-app/src/java/org/sakaiproject/tool/assessment/ui/listener/delivery/DeliveryActionListener.java $ * $Id: DeliveryActionListener.java 130878 2013-10-25 22:48:06Z ktsao@stanford.edu $ *********************************************************************************** * * Copyright (c) 2004, 2005, 2006, 2007, 2008, 2009 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.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.tool.assessment.ui.listener.delivery; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; import java.util.ResourceBundle; import java.util.Set; import java.util.StringTokenizer; import javax.faces.context.FacesContext; import javax.faces.event.AbortProcessingException; import javax.faces.event.ActionEvent; import javax.faces.event.ActionListener; import javax.faces.model.SelectItem; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.component.cover.ComponentManager; import org.sakaiproject.component.cover.ServerConfigurationService; import org.sakaiproject.event.api.Event; import org.sakaiproject.event.api.LearningResourceStoreService; import org.sakaiproject.event.api.LearningResourceStoreService.LRS_Actor; import org.sakaiproject.event.api.LearningResourceStoreService.LRS_Object; import org.sakaiproject.event.api.LearningResourceStoreService.LRS_Statement; import org.sakaiproject.event.api.LearningResourceStoreService.LRS_Verb; import org.sakaiproject.event.api.LearningResourceStoreService.LRS_Verb.SAKAI_VERB; import org.sakaiproject.event.cover.EventTrackingService; import org.sakaiproject.event.cover.NotificationService; import org.sakaiproject.tool.assessment.api.SamigoApiFactory; import org.sakaiproject.tool.assessment.data.dao.assessment.AssessmentAccessControl; import org.sakaiproject.tool.assessment.data.dao.assessment.EventLogData; import org.sakaiproject.tool.assessment.data.dao.grading.AssessmentGradingData; import org.sakaiproject.tool.assessment.data.dao.grading.ItemGradingAttachment; import org.sakaiproject.tool.assessment.data.dao.grading.ItemGradingData; import org.sakaiproject.tool.assessment.data.ifc.assessment.AnswerIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.AssessmentAccessControlIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.AssessmentFeedbackIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.AssessmentIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.EvaluationModelIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.ItemDataIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.ItemMetaDataIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.ItemTextIfc; import org.sakaiproject.tool.assessment.data.ifc.assessment.SectionDataIfc; import org.sakaiproject.tool.assessment.data.ifc.shared.TypeIfc; import org.sakaiproject.tool.assessment.facade.AgentFacade; import org.sakaiproject.tool.assessment.facade.EventLogFacade; import org.sakaiproject.tool.assessment.facade.PublishedAssessmentFacade; import org.sakaiproject.tool.assessment.services.GradingService; import org.sakaiproject.tool.assessment.services.assessment.PublishedAssessmentService; import org.sakaiproject.tool.assessment.shared.api.assessment.SecureDeliveryServiceAPI; import org.sakaiproject.tool.assessment.shared.api.assessment.SecureDeliveryServiceAPI.Phase; import org.sakaiproject.tool.assessment.shared.api.assessment.SecureDeliveryServiceAPI.PhaseStatus; import org.sakaiproject.tool.assessment.services.assessment.EventLogService; import org.sakaiproject.tool.assessment.ui.bean.delivery.ContentsDeliveryBean; import org.sakaiproject.tool.assessment.ui.bean.delivery.DeliveryBean; import org.sakaiproject.tool.assessment.ui.bean.delivery.FeedbackComponent; import org.sakaiproject.tool.assessment.ui.bean.delivery.FibBean; import org.sakaiproject.tool.assessment.ui.bean.delivery.FinBean; import org.sakaiproject.tool.assessment.ui.bean.delivery.ItemContentsBean; import org.sakaiproject.tool.assessment.ui.bean.delivery.MatchingBean; import org.sakaiproject.tool.assessment.ui.bean.delivery.MatrixSurveyBean; import org.sakaiproject.tool.assessment.ui.bean.delivery.SectionContentsBean; import org.sakaiproject.tool.assessment.ui.bean.delivery.SelectionBean; import org.sakaiproject.tool.assessment.ui.bean.evaluation.StudentScoresBean; import org.sakaiproject.tool.assessment.ui.bean.shared.PersonBean; import org.sakaiproject.tool.assessment.ui.listener.util.ContextUtil; import org.sakaiproject.tool.assessment.ui.model.delivery.TimedAssessmentGradingModel; import org.sakaiproject.tool.assessment.ui.queue.delivery.TimedAssessmentQueue; import org.sakaiproject.tool.assessment.ui.web.session.SessionUtil; import org.sakaiproject.tool.assessment.util.FormatException; import org.sakaiproject.util.FormattedText; import org.sakaiproject.util.ResourceLoader; /** * <p>Title: Samigo</p> * <p>Purpose: this module creates the lists of published assessments for the select index * <p>Description: Sakai Assessment Manager</p> * @author Ed Smiley * @version $Id: DeliveryActionListener.java 130878 2013-10-25 22:48:06Z ktsao@stanford.edu $ */ public class DeliveryActionListener implements ActionListener { static String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static Log log = LogFactory.getLog(DeliveryActionListener.class); //private static ContextUtil cu; private boolean resetPageContents = true; private long previewGradingId = (long)(Math.random() * 1000); private static ResourceBundle eventLogMessages = ResourceBundle.getBundle("org.sakaiproject.tool.assessment.bundle.EventLogMessages"); /** * ACTION. * @param ae * @throws AbortProcessingException */ public void processAction(ActionEvent ae) throws AbortProcessingException { log.debug("DeliveryActionListener.processAction() "); try { PersonBean person = (PersonBean) ContextUtil.lookupBean("person"); log.debug("**** MacNetscape="+person.getIsMacNetscapeBrowser()); // 1. get managed bean DeliveryBean delivery = (DeliveryBean) ContextUtil.lookupBean("delivery"); // set publishedId, note that id can be changed by isPreviewingMode() String id = getPublishedAssessmentId(delivery); String agent = getAgentString(); // 2. get assessment from deliveryBean if id matches. otherwise, this is the 1st time // that DeliveryActionListener is called, so pull it from DB PublishedAssessmentFacade publishedAssessment = getPublishedAssessment(delivery, id); int action = delivery.getActionMode(); if (DeliveryBean.REVIEW_ASSESSMENT == action && AssessmentIfc.RETRACT_FOR_EDIT_STATUS.equals(publishedAssessment.getStatus())) { // Bug 1547: If this is during review and the assessment is retracted for edit now, // there is no action needed (the outcome is set in BeginDeliveryActionListener). return; } // Clear elapsed time, set not timed out clearElapsedTime(delivery); // set show student score setShowStudentScore(delivery, publishedAssessment); setShowStudentQuestionScore(delivery, publishedAssessment); setDeliverySettings(delivery, publishedAssessment); // 3. there 3 types of navigation: by question (question will be displayed one at a time), // by part (questions in a part will be displayed together) or by assessment (i.e. // all questions will be displayed on one page). When navigating from TOC, a part number // signal that the navigation is either by question or by part. We then must set the // delivery bean to the right place. // However, it comes from Begin Assessment button clicks, we need to reset the indexes to 0 // Otherwise, the first question of the first part will not be displayed if (ae != null && ae.getComponent().getId().startsWith("beginAssessment")) { if (!delivery.getNavigation().equals("1")) { // If it comes from Begin Assessment button clicks (in Random assessment), reset the indexes to 0 log.debug("From Begin Assessment button clicks"); delivery.setPartIndex(0); delivery.setQuestionIndex(0); } // If it comes from Begin Assessment button clicks, reset isNoQuestion to false // because we want to always display the first page // Otherwise, if isNoQuestion set to true in the last delivery and // if the first part has no question, it will not be rendered // See getPageContentsByQuestion() for more details // Of course it is better to do this inside getPageContentsByQuestion() // However, ae is not passed in getPageContentsByQuestion() // and there are multiple places to modify if I want to get ae inside getPageContentsByQuestion() delivery.setNoQuestions(false); } else { // If from table of contents page, there is no parameters like partnumber or questionnumber if (!delivery.getFromTableOfContents()) { // If comes from TOC, set the indexes from request parameters goToRightQuestionFromTOC(delivery); } else { delivery.setFromTableOfContents(false); } } // 4. this purpose of this listener is to integrated itemGradingData and // assessmentGradingData to the publishedAssessment during these 3 processes: // taking assessment, reviewing assessment and grading assessment by question. // When taking or reviewing an assessment, BeginDeliveryActionListener is called // by an event in the jsf page to retrieve the publishedAssessment. When grading // assessment, StudentScoreListener is called to retrieve the published Assessment. // itemGradingHash will end up with // (Long publishedItemId, ArrayList itemGradingDatas) and // (String "sequence"+itemId, Integer sequence) and // (String "items", Long itemscount) HashMap itemGradingHash = new HashMap(); GradingService service = new GradingService(); PublishedAssessmentService pubService = new PublishedAssessmentService(); AssessmentGradingData ag = null; SecureDeliveryServiceAPI secureDelivery = SamigoApiFactory.getInstance().getSecureDeliveryServiceAPI(); boolean isFirstTimeBegin = false; switch (action){ case 2: // preview assessment setFeedbackMode(delivery); break; case 3: // Review assessment setFeedbackMode(delivery); //this determine if we should gather the itemGrading Integer scoringoption = publishedAssessment.getEvaluationModel().getScoringType(); String assessmentGradingId = ContextUtil.lookupParam("assessmentGradingId"); if (("true").equals(delivery.getFeedback())){ itemGradingHash = new HashMap(); if (delivery.getFeedbackComponent().getShowResponse() || delivery.getFeedbackComponent().getShowStudentQuestionScore()) itemGradingHash = service.getSubmitData(id, agent, scoringoption, assessmentGradingId); ag = setAssessmentGradingFromItemData(delivery, itemGradingHash, false); delivery.setAssessmentGrading(ag); } setDisplayByAssessment(delivery); //setDeliveryFeedbackOnforEvaluation(delivery); //setGraderComment(delivery); FeedbackComponent component = new FeedbackComponent(); AssessmentFeedbackIfc info = (AssessmentFeedbackIfc) publishedAssessment.getAssessmentFeedback(); if ( info != null) { component.setAssessmentFeedback(info); } delivery.setFeedbackComponent(component); AssessmentGradingData agData = null; if (EvaluationModelIfc.LAST_SCORE.equals(scoringoption)){ agData = (AssessmentGradingData) service.getLastSubmittedAssessmentGradingByAgentId(id, agent, assessmentGradingId); } else { agData = (AssessmentGradingData) service.getHighestSubmittedAssessmentGrading(id, agent, assessmentGradingId); } if (agData == null) { delivery.setOutcome("reviewAssessmentError"); return; } log.debug("GraderComments: getComments()" + agData.getComments()); delivery.setGraderComment(agData.getComments()); delivery.setAssessmentGradingId(agData.getAssessmentGradingId()); delivery.setOutcome("takeAssessment"); delivery.setSecureDeliveryHTMLFragment( "" ); delivery.setBlockDelivery( false ); if ( secureDelivery.isSecureDeliveryAvaliable() ) { String moduleId = publishedAssessment.getAssessmentMetaDataByLabel( SecureDeliveryServiceAPI.MODULE_KEY ); if ( moduleId != null && ! SecureDeliveryServiceAPI.NONE_ID.equals( moduleId ) ) { HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest(); PhaseStatus status = secureDelivery.validatePhase(moduleId, Phase.ASSESSMENT_REVIEW, publishedAssessment, request ); delivery.setSecureDeliveryHTMLFragment( secureDelivery.getHTMLFragment(moduleId, publishedAssessment, request, Phase.ASSESSMENT_REVIEW, status, new ResourceLoader().getLocale() ) ); if ( PhaseStatus.FAILURE == status ) { delivery.setOutcome( "secureDeliveryError" ); delivery.setBlockDelivery( true ); } } } break; case 4: // Grade assessment String gradingData = ContextUtil.lookupParam("gradingData"); itemGradingHash = service.getStudentGradingData(gradingData); delivery.setAssessmentGradingId(Long.valueOf(gradingData)); ag = setAssessmentGradingFromItemData(delivery, itemGradingHash, false); delivery.setAssessmentGrading(ag); setDisplayByAssessment(delivery); setFeedbackMode(delivery); setDeliveryFeedbackOnforEvaluation(delivery); setGraderComment(delivery); break; case 1: // Take assessment case 5: // Take assessment via url log.debug("**** DeliveryActionListener #0"); if (ae != null && ae.getComponent().getId().startsWith("beginAssessment")) { // #1. check password if (!delivery.getSettings().getUsername().equals("")) { if ("passwordAccessError".equals(delivery.validatePassword())) { return; } } // #2. check IP if (delivery.getSettings().getIpAddresses() != null && !delivery.getSettings().getIpAddresses().isEmpty()) { if ("ipAccessError".equals(delivery.validateIP())) { return; } } // #3. secure delivery START phase if ( secureDelivery.isSecureDeliveryAvaliable() ) { String moduleId = publishedAssessment.getAssessmentMetaDataByLabel( SecureDeliveryServiceAPI.MODULE_KEY ); if ( moduleId != null && ! SecureDeliveryServiceAPI.NONE_ID.equals( moduleId ) ) { HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest(); PhaseStatus status = secureDelivery.validatePhase(moduleId, Phase.ASSESSMENT_START, publishedAssessment, request ); if ( PhaseStatus.FAILURE == status ) { return; } } } } // If this is a linear access and user clicks on Show Feedback, we do not // get data from db. Use delivery bean instead if (delivery.getNavigation().equals("1") && ae != null && "showFeedback".equals(ae.getComponent().getId())) { log.debug("Do not get data from db if it is linear access and the action is show feedback but..."); log.debug("except file upload and audio questions"); ag = delivery.getAssessmentGrading(); Set itemGradingSet = ag.getItemGradingSet(); Iterator iter = itemGradingSet.iterator(); while (iter.hasNext()) { ItemGradingData data = (ItemGradingData) iter.next(); ArrayList thisone = (ArrayList) itemGradingHash.get(data.getPublishedItemId()); if (thisone == null) { thisone = new ArrayList(); } thisone.add(data); itemGradingHash.put(data.getPublishedItemId(), thisone); } // For file upload and audio questions, adding the corresponding itemGradingData into itemGradingHash and itemGradingSet to display correctly in delivery // this hash compose (itemGradingId, array list of MediaData) HashMap mediaItemGradingHash = service.getMediaItemGradingHash(ag.getAssessmentGradingId()); Set<Map.Entry<Long, ArrayList>> set = mediaItemGradingHash.entrySet(); for (Map.Entry<Long, ArrayList> me : set) { Long publishedItemId = (Long) me.getKey(); ArrayList al = (ArrayList) me.getValue(); ArrayList itemGradingArray = (ArrayList) itemGradingHash.get(publishedItemId); if (itemGradingArray != null) { itemGradingArray.addAll(al); } else { itemGradingArray = new ArrayList(); itemGradingArray.addAll(al); } itemGradingHash.put(publishedItemId, itemGradingArray); itemGradingSet.addAll(itemGradingArray); ag.setItemGradingSet(itemGradingSet); } } else { log.debug("Get data from db otherwise"); // this returns a HashMap with (publishedItemId, itemGrading) itemGradingHash = service.getLastItemGradingData(id, agent); // log.debug("**** DeliveryActionListener #1"); if (itemGradingHash!=null && itemGradingHash.size()>0){ log.debug("**** DeliveryActionListener #1a"); ag = setAssessmentGradingFromItemData(delivery, itemGradingHash, true); setAttemptDateIfNull(ag); } else { ag = service.getLastSavedAssessmentGradingByAgentId(id, agent); if (ag == null) { ag = createAssessmentGrading(publishedAssessment); isFirstTimeBegin = true; } else { setAttemptDateIfNull(ag); } } delivery.setAssessmentGrading(ag); } log.debug("**** DeliveryAction, itemgrading size="+ag.getItemGradingSet().size()); delivery.setAssessmentGradingId(delivery.getAssessmentGrading().getAssessmentGradingId()); // ag can't be null beyond this point and must have persisted to DB // version 2.1.1 requirement setFeedbackMode(delivery); if (ae != null && ae.getComponent().getId().startsWith("beginAssessment")) { setTimer(delivery, publishedAssessment, true, isFirstTimeBegin); setStatus(delivery, pubService, Long.valueOf(id)); EventLogService eventService = new EventLogService(); EventLogFacade eventLogFacade = new EventLogFacade(); String agentEid = AgentFacade.getEid(); //ONC-3500 if(agentEid == null || agentEid.equals("")){ agentEid= "N/A"; } //set event log data EventLogData eventLogData = new EventLogData(); eventLogData.setAssessmentId(Long.valueOf(id)); eventLogData.setProcessId(delivery.getAssessmentGradingId()); eventLogData.setStartDate(new Date()); eventLogData.setTitle(publishedAssessment.getTitle()); eventLogData.setUserEid(agentEid); String site_id = AgentFacade.getCurrentSiteId(); //take assessment via url if(site_id == null) { PublishedAssessmentService publishedAssessmentService = new PublishedAssessmentService(); site_id = publishedAssessmentService.getPublishedAssessmentOwner(Long.valueOf(delivery.getAssessmentId())); } eventLogData.setSiteId(site_id); eventLogData.setErrorMsg(eventLogMessages.getString("no_submission")); eventLogData.setEndDate(null); eventLogData.setEclipseTime(null); eventLogFacade.setData(eventLogData); eventService.saveOrUpdateEventLog(eventLogFacade); if (action == DeliveryBean.TAKE_ASSESSMENT) { StringBuffer eventRef = new StringBuffer("publishedAssessmentId="); eventRef.append(delivery.getAssessmentId()); eventRef.append(", agentId="); eventRef.append(getAgentString()); if (delivery.isTimeRunning()) { eventRef.append(", elapsed="); eventRef.append(delivery.getTimeElapse()); eventRef.append(", remaining="); int timeRemaining = Integer.parseInt(delivery.getTimeLimit()) - Integer.parseInt(delivery.getTimeElapse()); eventRef.append(timeRemaining); } Event event = EventTrackingService.newEvent("sam.assessment.take", "siteId=" + site_id + ", " + eventRef.toString(), true); EventTrackingService.post(event); registerIrss(delivery, event, false); } else if (action == DeliveryBean.TAKE_ASSESSMENT_VIA_URL) { StringBuffer eventRef = new StringBuffer("publishedAssessmentId="); eventRef.append(delivery.getAssessmentId()); eventRef.append(", agentId="); eventRef.append(getAgentString()); if (delivery.isTimeRunning()) { eventRef.append(", elapsed="); eventRef.append(delivery.getTimeElapse()); eventRef.append(", remaining="); int timeRemaining = Integer.parseInt(delivery.getTimeLimit()) - Integer.parseInt(delivery.getTimeElapse()); eventRef.append(timeRemaining); } Event event = EventTrackingService.newEvent("sam.assessment.take.via_url", "siteId=" + site_id + ", " + eventRef.toString(), site_id, true, NotificationService.NOTI_REQUIRED); EventTrackingService.post(event); registerIrss(delivery, event, true); } } else { setTimer(delivery, publishedAssessment, false, false); } // extend session time out SessionUtil.setSessionTimeout(FacesContext.getCurrentInstance(), delivery, true); log.debug("****Set begin time " + delivery.getBeginTime()); log.debug("****Set elapsed time " + delivery.getTimeElapse()); break; default: break; } // overload itemGradingHash with the sequence in case renumbering is turned off. overloadItemData(delivery, itemGradingHash, publishedAssessment); // get table of contents HashMap publishedAnswerHash = pubService.preparePublishedAnswerHash(publishedAssessment); delivery.setTableOfContents(getContents(publishedAssessment, itemGradingHash, delivery, publishedAnswerHash)); // get current page contents log.debug("**** resetPageContents="+this.resetPageContents); // If it comes from Show Feedback link clicks, call getShowFeedbackPageContents() to // reset the partIndex and questionIndex (the last part of SAK-5750) if (this.resetPageContents){ if (ae != null && ae.getComponent().getId().equals("showFeedback")) { delivery.setPageContents(getShowFeedbackPageContents(publishedAssessment, delivery, itemGradingHash, publishedAnswerHash)); } else { delivery.setPageContents(getPageContents(publishedAssessment, delivery, itemGradingHash, publishedAnswerHash)); } } } catch (RuntimeException e) { DeliveryBean delivery = (DeliveryBean) ContextUtil.lookupBean("delivery"); String id = getPublishedAssessmentId(delivery); String agentEid = AgentFacade.getEid(); if(agentEid == null || agentEid.equals("")) { agentEid= "N/A"; } PublishedAssessmentFacade publishedAssessment = getPublishedAssessment(delivery, id); EventLogService eventService = new EventLogService(); EventLogFacade eventLogFacade = new EventLogFacade(); EventLogData eventLogData = null; List eventLogDataList = eventService.getEventLogData(delivery.getAssessmentGradingId()); if(eventLogDataList != null && eventLogDataList.size() > 0) { eventLogData= (EventLogData) eventLogDataList.get(0); eventLogData.setErrorMsg(eventLogMessages.getString("error_begin")); } else { eventLogData = new EventLogData(); eventLogData.setErrorMsg(eventLogMessages.getString("error_begin")); eventLogData.setAssessmentId(Long.valueOf(id)); eventLogData.setProcessId(delivery.getAssessmentGradingId()); eventLogData.setStartDate(new Date()); eventLogData.setTitle(publishedAssessment.getTitle()); eventLogData.setUserEid(agentEid); String site_id =AgentFacade.getCurrentSiteId(); //take assessment via url if(site_id == null) { PublishedAssessmentService publishedAssessmentService = new PublishedAssessmentService(); site_id = publishedAssessmentService.getPublishedAssessmentOwner(Long.valueOf(delivery.getAssessmentId())); } eventLogData.setSiteId(site_id); eventLogData.setEndDate(null); eventLogData.setEclipseTime(null); } eventLogFacade.setData(eventLogData); eventService.saveOrUpdateEventLog(eventLogFacade); throw e; } } protected void registerIrss(DeliveryBean delivery, Event event, boolean isViaURL) { LearningResourceStoreService lrss = (LearningResourceStoreService) ComponentManager .get("org.sakaiproject.event.api.LearningResourceStoreService"); if (null != lrss && lrss.getEventActor(event) != null) { StringBuffer lrssMetaInfo = new StringBuffer("Assesment: " + delivery.getAssessmentTitle()); lrssMetaInfo.append(", Past Due?: " + delivery.getPastDue()); if (isViaURL) { lrssMetaInfo.append(", Assesment taken via URL."); } lrss.registerStatement(getStatementForTakeAssessment(lrss.getEventActor(event), event, lrssMetaInfo.toString()), "samigo"); } } /** * Look up item grading data and set assesment grading data from it or, * if there is none set null if setNullOK. * @param delivery the delivery bean * @param itemGradingHash the itemGradingHash hash map * @param setNullOK if there is none set null if true */ protected AssessmentGradingData setAssessmentGradingFromItemData(DeliveryBean delivery, HashMap itemGradingHash, boolean setNullOK) { AssessmentGradingData agrading = null; Iterator keys = itemGradingHash.keySet().iterator(); // for each publishedItem, looks like we are getting the 1st itemGradingData GradingService gradingService = new GradingService(); if (keys.hasNext()) { ItemGradingData igd = (ItemGradingData) ( (ArrayList) itemGradingHash.get( keys.next())).toArray()[0]; AssessmentGradingData agd = gradingService.load(igd.getAssessmentGradingId().toString(), false); agd.setItemGradingSet(gradingService.getItemGradingSet(agd.getAssessmentGradingId().toString())); if (!agd.getForGrade().booleanValue()){ log.debug("setAssessmentGradingFromItemData agd.getTimeElapsed(): " + agd.getTimeElapsed()); log.debug("setAssessmentGradingFromItemData delivery.getTimeElapse(): " + delivery.getTimeElapse()); agrading = agd; } else{ // if assessmentGradingData has been submitted for grade, then // we need to reset delivery.assessmentGrading - a problem review from fixing SAK-1781 if (setNullOK) agrading=null; } } else { if (setNullOK) agrading=null; } return agrading; //log.info("**** delivery grdaing"+delivery.getAssessmentGrading()); } /** * Put the setShows on. * @param delivery the delivery bean */ private void setDeliveryFeedbackOnforEvaluation(DeliveryBean delivery) { if (delivery.getFeedbackComponent() == null) { delivery.setFeedbackComponent(new FeedbackComponent()); } delivery.getFeedbackComponent().setShowCorrectResponse(true); delivery.getFeedbackComponent().setShowGraderComment(true); delivery.getFeedbackComponent().setShowItemLevel(true); delivery.getFeedbackComponent().setShowQuestion(true); delivery.getFeedbackComponent().setShowResponse(true); delivery.getFeedbackComponent().setShowSelectionLevel(true); delivery.getFeedbackComponent().setShowStats(true); delivery.getFeedbackComponent().setShowStudentScore(true); delivery.getFeedbackComponent().setShowStudentQuestionScore(true); } /** * Sets the delivery bean to the right place when navigating from TOC * @param delivery * @throws java.lang.NumberFormatException */ private void goToRightQuestionFromTOC(DeliveryBean delivery) throws NumberFormatException { if (ContextUtil.lookupParam("partnumber") != null && !ContextUtil.lookupParam("partnumber").trim().equals("") && ContextUtil.lookupParam("questionnumber") != null && !ContextUtil.lookupParam("questionnumber").trim().equals("")) { delivery.setPartIndex(Integer.valueOf (ContextUtil.lookupParam("partnumber")).intValue() - 1); delivery.setQuestionIndex(Integer.valueOf (ContextUtil.lookupParam("questionnumber")).intValue() - 1); } } /** * Gets a table of contents bean * @param publishedAssessment the published assessment * @return */ protected ContentsDeliveryBean getContents(PublishedAssessmentFacade publishedAssessment, HashMap itemGradingHash, DeliveryBean delivery, HashMap publishedAnswerHash) { ContentsDeliveryBean contents = new ContentsDeliveryBean(); double currentScore = 0; double maxScore = 0; // get parts ArrayList partSet = publishedAssessment.getSectionArraySorted(); Iterator iter = partSet.iterator(); ArrayList partsContents = new ArrayList(); while (iter.hasNext()) { SectionContentsBean partBean = getPartBean( (SectionDataIfc) iter.next(), itemGradingHash, delivery, publishedAnswerHash); partBean.setNumParts(Integer.toString(partSet.size())); currentScore += partBean.getPoints(); maxScore += partBean.getMaxPoints(); partsContents.add(partBean); } contents.setCurrentScore(currentScore); contents.setMaxScore(maxScore); contents.setPartsContents(partsContents); return contents; } /** * Gets a contents bean for the current page. * Really, just a wrapper utility to delegate to whichever * method handles the format being used. * * @todo these should actually take a copy of contents and filter it * for the page unstead of doing a recompute, which is less efficient * @param publishedAssessment the published assessment * @return */ public ContentsDeliveryBean getPageContents( PublishedAssessmentFacade publishedAssessment, DeliveryBean delivery, HashMap itemGradingHash, HashMap publishedAnswerHash) { if (delivery.getSettings().isFormatByAssessment()) { return getPageContentsByAssessment(publishedAssessment, itemGradingHash, delivery, publishedAnswerHash); } int itemIndex = delivery.getQuestionIndex(); int sectionIndex = delivery.getPartIndex(); if (delivery.getSettings().isFormatByPart()) { return getPageContentsByPart(publishedAssessment, itemIndex, sectionIndex, itemGradingHash, delivery, publishedAnswerHash); } else if (delivery.getSettings().isFormatByQuestion()) { return getPageContentsByQuestion(publishedAssessment, itemIndex, sectionIndex, itemGradingHash, delivery, publishedAnswerHash); } // default... ...shouldn't get here :O log.warn("delivery.getSettings().isFormatBy... is NOT set!"); return getPageContentsByAssessment(publishedAssessment, itemGradingHash, delivery, publishedAnswerHash); } /** * When user clicks on Show Feedback, this method gets a contents bean for the current page. * The difference of the above one is we reset partIndex/questionIndex to make the first * question to be seen on the top (the last part of SAK-5750). * * @todo these should actually take a copy of contents and filter it * for the page unstead of doing a recompute, which is less efficient * @param publishedAssessment the published assessment * @return */ public ContentsDeliveryBean getShowFeedbackPageContents( PublishedAssessmentFacade publishedAssessment, DeliveryBean delivery, HashMap itemGradingHash, HashMap publishedAnswerHash) { if (delivery.getSettings().isFormatByAssessment()) { delivery.setPartIndex(0); delivery.setQuestionIndex(0); return getPageContentsByAssessment(publishedAssessment, itemGradingHash, delivery, publishedAnswerHash); } if (delivery.getSettings().isFormatByPart()) { delivery.setQuestionIndex(0); return getPageContentsByPart(publishedAssessment, delivery.getQuestionIndex(), delivery.getPartIndex(), itemGradingHash, delivery, publishedAnswerHash); } else if (delivery.getSettings().isFormatByQuestion()) { return getPageContentsByQuestion(publishedAssessment, delivery.getQuestionIndex(), delivery.getPartIndex(), itemGradingHash, delivery, publishedAnswerHash); } // default... ...shouldn't get here :O log.warn("delivery.getSettings().isFormatBy... is NOT set!"); delivery.setPartIndex(0); delivery.setQuestionIndex(0); return getPageContentsByAssessment(publishedAssessment, itemGradingHash, delivery, publishedAnswerHash); } /** * Gets a contents bean for the current page if is format by assessment. * * @param publishedAssessment the published assessment * @return ContentsDeliveryBean for page */ private ContentsDeliveryBean getPageContentsByAssessment( PublishedAssessmentFacade publishedAssessment, HashMap itemGradingHash, DeliveryBean delivery, HashMap publishedAnswerHash) { ContentsDeliveryBean contents = new ContentsDeliveryBean(); double currentScore = 0; double maxScore = 0; // get parts ArrayList partSet = publishedAssessment.getSectionArraySorted(); Iterator iter = partSet.iterator(); ArrayList partsContents = new ArrayList(); while (iter.hasNext()) { SectionContentsBean partBean = getPartBean( (SectionDataIfc) iter.next(), itemGradingHash, delivery, publishedAnswerHash); partBean.setNumParts(Integer.toString(partSet.size())); if (partBean.getItemContentsSize().equals("0")) { log.debug("getPageContentsByAssessment(): no question"); partBean.setNoQuestions(true); } currentScore += partBean.getPoints(); maxScore += partBean.getMaxPoints(); partsContents.add(partBean); } delivery.setPrevious(false); delivery.setContinue(false); contents.setCurrentScore(currentScore); contents.setMaxScore(maxScore); contents.setPartsContents(partsContents); contents.setShowStudentScore(delivery.isShowStudentScore()); return contents; } /** * Gets a contents bean for the current page if is format by part. * * @param publishedAssessment the published assessment * @param itemIndex zero based item offset in part * @param sectionIndex zero based section offset in assessment * @return ContentsDeliveryBean for page */ private ContentsDeliveryBean getPageContentsByPart( PublishedAssessmentFacade publishedAssessment, int itemIndex, int sectionIndex, HashMap itemGradingHash, DeliveryBean delivery, HashMap publishedAnswerHash) { ContentsDeliveryBean contents = new ContentsDeliveryBean(); double currentScore = 0; double maxScore = 0; int sectionCount = 0; // get parts ArrayList partSet = publishedAssessment.getSectionArraySorted(); Iterator iter = partSet.iterator(); ArrayList partsContents = new ArrayList(); while (iter.hasNext()) { SectionContentsBean partBean = getPartBean( (SectionDataIfc) iter.next(), itemGradingHash, delivery, publishedAnswerHash); partBean.setNumParts(Integer.toString(partSet.size())); if (partBean.getItemContentsSize().equals("0")) { log.debug("getPageContentsByPart(): no question"); partBean.setNoQuestions(true); } currentScore += partBean.getPoints(); maxScore += partBean.getMaxPoints(); if (sectionCount++ == sectionIndex) { partsContents.add(partBean); if (iter.hasNext()) { delivery.setContinue(true); } else { delivery.setContinue(false); } if (sectionCount > 1) { delivery.setPrevious(true); } else { delivery.setPrevious(false); } } } contents.setCurrentScore(currentScore); contents.setMaxScore(maxScore); contents.setPartsContents(partsContents); contents.setShowStudentScore(delivery.isShowStudentScore()); return contents; } /** * Gets a contents bean for the current page if is format by question. * * @param publishedAssessment the published assessment * @param itemIndex zero based item offset in part * @param sectionIndex zero based section offset in assessment * @return ContentsDeliveryBean for page */ private ContentsDeliveryBean getPageContentsByQuestion( PublishedAssessmentFacade publishedAssessment, int itemIndex, int sectionIndex, HashMap itemGradingHash, DeliveryBean delivery, HashMap publishedAnswerHash) { ContentsDeliveryBean contents = new ContentsDeliveryBean(); double currentScore = 0; double maxScore = 0; int sectionCount = 0; int questionCount = 0; // This is to increment the part if we run // out of questions // get parts ArrayList partSet = publishedAssessment.getSectionArraySorted(); Iterator iter = partSet.iterator(); ArrayList partsContents = new ArrayList(); if (itemIndex < 0) { sectionIndex--; delivery.setPartIndex(sectionIndex); } while (iter.hasNext()) // has next part { SectionDataIfc secFacade = (SectionDataIfc) iter.next(); SectionContentsBean partBean = getPartBean(secFacade, itemGradingHash, delivery, publishedAnswerHash); partBean.setNumParts(Integer.toString(partSet.size())); currentScore += partBean.getPoints(); maxScore += partBean.getMaxPoints(); //questionCount = secFacade.getItemSet().size(); // need to get ItemArraySort, insteand of getItemSet, to return corr number for random draw parts ArrayList itemlist = secFacade.getItemArray(); long seed = getSeed(secFacade, delivery, (long) AgentFacade.getAgentString().hashCode()); ArrayList sortedlist = getItemArraySortedWithRandom(secFacade, itemlist, seed); questionCount = sortedlist.size(); if ((delivery.getNoQuestions() || questionCount != 0) && itemIndex > (questionCount - 1) && sectionCount == sectionIndex) { sectionIndex++; delivery.setPartIndex(sectionIndex); itemIndex = 0; delivery.setQuestionIndex(itemIndex); delivery.setNoQuestions(false); } if (itemIndex < 0 && sectionCount == sectionIndex) { itemIndex = questionCount - 1; delivery.setQuestionIndex(itemIndex); } if (sectionCount++ == sectionIndex) { SectionContentsBean partBeanWithQuestion = this.getPartBeanWithOneQuestion(secFacade, itemIndex, itemGradingHash, delivery, publishedAnswerHash); partBeanWithQuestion.setNumParts(Integer.toString(partSet.size())); partsContents.add(partBeanWithQuestion); if (questionCount == 0) { partBeanWithQuestion.setNoQuestions(true); delivery.setNoQuestions(true); } else { partBeanWithQuestion.setNoQuestions(false); delivery.setNoQuestions(false); } if (iter.hasNext() || itemIndex < (questionCount - 1)) { delivery.setContinue(true); } else { delivery.setContinue(false); } if (itemIndex > 0 || sectionIndex > 0) { delivery.setPrevious(true); } else { delivery.setPrevious(false); } } } contents.setCurrentScore(currentScore); contents.setMaxScore(maxScore); contents.setPartsContents(partsContents); contents.setShowStudentScore(delivery.isShowStudentScore()); return contents; } /** * Populate a SectionContentsBean properties and populate with ItemContentsBean * @param part this section * @return */ private SectionContentsBean getPartBean(SectionDataIfc part, HashMap itemGradingHash, DeliveryBean delivery, HashMap publishedAnswerHash) { double maxPoints = 0; double points = 0; int unansweredQuestions = 0; //SectionContentsBean sec = new SectionContentsBean(); //sec.setSectionId(part.getSectionId().toString()); // daisy change to use this existing constructor instead 11/09/05 SectionContentsBean sec = new SectionContentsBean(part); ArrayList itemSet = null; ArrayList itemlist = part.getItemArray(); long seed = 0; if (delivery.getActionMode()==DeliveryBean.GRADE_ASSESSMENT) { StudentScoresBean studentscorebean = (StudentScoresBean) ContextUtil.lookupBean("studentScores"); seed = getSeed(part, delivery, (long) studentscorebean.getStudentId().hashCode()); } else { seed = getSeed(part, delivery, (long) AgentFacade.getAgentString().hashCode()); } itemSet= getItemArraySortedWithRandom(part, itemlist, seed); // i think this is already set by new SectionContentsBean(part) - daisyf sec.setQuestions(itemSet.size()); if (delivery.getSettings().getItemNumbering().equals (AssessmentAccessControl.RESTART_NUMBERING_BY_PART.toString())) { sec.setNumbering(itemSet.size()); } else { sec.setNumbering( ( (Long) itemGradingHash.get("items")).intValue()); } sec.setText(part.getTitle()); sec.setDescription(part.getDescription()); // i think these are already set by new SectionContentsBean(part) - daisyf sec.setNumber("" + part.getSequence()); sec.setMetaData(part); Iterator iter = itemSet.iterator(); ArrayList itemContents = new ArrayList(); int i = 0; while (iter.hasNext()) { ItemDataIfc thisitem = (ItemDataIfc) iter.next(); ItemContentsBean itemBean = getQuestionBean(thisitem, itemGradingHash, delivery, publishedAnswerHash); // Deal with numbering itemBean.setNumber(++i); if (delivery.getSettings().getItemNumbering().equals (AssessmentAccessControl.RESTART_NUMBERING_BY_PART.toString())) { itemBean.setSequence(Integer.toString(itemBean.getNumber())); } else { itemBean.setSequence( ( (Integer) itemGradingHash.get("sequence" + thisitem.getItemId().toString())).toString()); } // scoring maxPoints += itemBean.getMaxPoints(); points += itemBean.getExactPoints(); itemBean.setShowStudentScore(delivery.isShowStudentScore()); itemBean.setShowStudentQuestionScore(delivery.isShowStudentQuestionScore()); if (itemBean.isUnanswered()) { unansweredQuestions++; } itemContents.add(itemBean); } // scoring information sec.setMaxPoints(maxPoints); sec.setPoints(points); sec.setShowStudentQuestionScore(delivery.isShowStudentQuestionScore()); sec.setUnansweredQuestions(unansweredQuestions); sec.setItemContents(itemContents); sec.setAttachmentList(part.getSectionAttachmentList()); return sec; } /** * Populate a SectionContentsBean properties and populate with ItemContentsBean * @param part this section * @return */ private SectionContentsBean getPartBeanWithOneQuestion( SectionDataIfc part, int itemIndex, HashMap itemGradingHash, DeliveryBean delivery, HashMap publishedAnswerHash) { double maxPoints = 0; double points = 0; int unansweredQuestions = 0; int itemCount = 0; //SectionContentsBean sec = new SectionContentsBean(); SectionContentsBean sec = new SectionContentsBean(part); ArrayList itemlist = part.getItemArray(); long seed = getSeed(part, delivery, (long) AgentFacade.getAgentString().hashCode()); ArrayList itemSet= getItemArraySortedWithRandom(part, itemlist, seed); sec.setQuestions(itemSet.size()); if (delivery.getSettings().getItemNumbering().equals (AssessmentAccessControl.RESTART_NUMBERING_BY_PART.toString())) { sec.setNumbering(itemSet.size()); } else { sec.setNumbering( ( (Long) itemGradingHash.get("items")).intValue()); } sec.setText(part.getTitle()); sec.setDescription(part.getDescription()); sec.setNumber("" + part.getSequence()); // get items Iterator iter = itemSet.iterator(); ArrayList itemContents = new ArrayList(); int i = 0; while (iter.hasNext()) { ItemDataIfc thisitem = (ItemDataIfc) iter.next(); ItemContentsBean itemBean = getQuestionBean(thisitem, itemGradingHash, delivery, publishedAnswerHash); // Numbering itemBean.setNumber(++i); if (delivery.getSettings().getItemNumbering().equals (AssessmentAccessControl.RESTART_NUMBERING_BY_PART.toString())) { itemBean.setSequence(Integer.toString(itemBean.getNumber())); } else { itemBean.setSequence( ( (Integer) itemGradingHash.get("sequence" + thisitem.getItemId().toString())).toString()); } // scoring maxPoints += itemBean.getMaxPoints(); points += itemBean.getExactPoints(); itemBean.setShowStudentScore(delivery.isShowStudentScore()); itemBean.setShowStudentQuestionScore(delivery.isShowStudentQuestionScore()); if (itemBean.isUnanswered()) { unansweredQuestions++; } if (itemCount++ == itemIndex) { itemContents.add(itemBean); } } // scoring information sec.setMaxPoints(maxPoints); sec.setPoints(points); sec.setShowStudentQuestionScore(delivery.isShowStudentQuestionScore()); sec.setUnansweredQuestions(unansweredQuestions); sec.setItemContents(itemContents); return sec; } /** * populate a single ItemContentsBean from an item for delivery * @param item an Item * @return */ private ItemContentsBean getQuestionBean(ItemDataIfc item, HashMap itemGradingHash, DeliveryBean delivery, HashMap publishedAnswerHash) { ItemContentsBean itemBean = new ItemContentsBean(); itemBean.setItemData(item); itemBean.setMaxPoints(item.getScore().doubleValue()); itemBean.setPoints( (double) 0); // update maxNumAttempts for audio if (item.getTriesAllowed() != null) { itemBean.setTriesAllowed(item.getTriesAllowed()); } // save timeallowed for audio recording if (item.getDuration() != null) { itemBean.setDuration(item.getDuration()); } itemBean.setItemGradingDataArray ( (ArrayList) itemGradingHash.get(item.getItemId())); if (itemBean.getItemGradingDataArray().size() > 0) { itemBean.setItemGradingIdForFilePicker(((ItemGradingData) itemBean.getItemGradingDataArray().get(0)).getItemGradingId()); } // Set comments and points Iterator i = itemBean.getItemGradingDataArray().iterator(); ArrayList itemGradingAttachmentList = new ArrayList(); while (i.hasNext()) { ItemGradingData data = (ItemGradingData) i.next(); // All itemgradingdata comments for the same item are identical <- u sure? daisyf itemBean.setGradingComment(data.getComments()); if (data.getAutoScore() != null) { itemBean.setPoints(itemBean.getExactPoints() + data.getAutoScore().doubleValue()); } // set attempts remaining for audio, there is only one itemGradingData // per question in this case if (data.getAttemptsRemaining() !=null ){ itemBean.setAttemptsRemaining(data.getAttemptsRemaining()); } // set the itemGradingAttachment only for Review and Grading flows because itemGradingAttachment // can exist in these two flows only (grader can only enter comments for submitted assessments) if (delivery.getActionMode() == 3 || delivery.getActionMode() == 4) { itemGradingAttachmentList.addAll(data.getItemGradingAttachmentList()); } else { itemGradingAttachmentList.addAll(new ArrayList<ItemGradingAttachment>()); } //itemBean.setItemGradingAttachmentList(data.getItemGradingAttachmentList()); } itemBean.setItemGradingAttachmentList(itemGradingAttachmentList); // set question feedback. if (item.getTypeId().equals(TypeIfc.ESSAY_QUESTION) || item.getTypeId().equals(TypeIfc.FILE_UPLOAD) || item.getTypeId().equals(TypeIfc.MULTIPLE_CHOICE_SURVEY) || item.getTypeId().equals(TypeIfc.AUDIO_RECORDING) || item.getTypeId().equals(TypeIfc.MATRIX_CHOICES_SURVEY)) { itemBean.setFeedback(item.getGeneralItemFeedback()); } else if ( itemBean.getMaxPoints()>0) { // // This is not really needed because the next Else{} will cover all other question types. // However it's much cheaper to check scores rather than looping through and check each answers. // I'm keeping it here. In most cases, this condition will be met. if (itemBean.getExactPoints() >= itemBean.getMaxPoints()) { itemBean.setFeedback(item.getCorrectItemFeedback()); } else { itemBean.setFeedback(item.getInCorrectItemFeedback()); } } else { // run this check if the question is worth 0 points. see SAK-5669 // In this case, we can't just check the scores to determine which feedback to show. // this doesn't happen very often. ArrayList itemgradingList = itemBean.getItemGradingDataArray(); Iterator iterAnswer = itemgradingList.iterator(); boolean haswronganswer =true; HashMap fibmap = new HashMap(); int mcmc_match_counter = 0; // if no answers yet, then display incorrect feedback. // if there are answers, then initialize haswronganswer =false; // correct feedback if (iterAnswer.hasNext()){ haswronganswer =false; } //calculate total # of correct answers. int correctAnswers = 0; if ((item.getTypeId().equals(TypeIfc.MULTIPLE_CORRECT) )|| (item.getTypeId().equals(TypeIfc.MULTIPLE_CORRECT_SINGLE_SELECTION) )||(item.getTypeId().equals(TypeIfc.MATCHING) )){ Iterator itemTextIter = item.getItemTextArray().iterator(); while (itemTextIter.hasNext()){ ItemTextIfc itemText = (ItemTextIfc) itemTextIter.next(); ArrayList answerArray = itemText.getAnswerArray(); if (answerArray != null){ for (int indexAnswer =0; indexAnswer<answerArray.size(); indexAnswer++){ AnswerIfc a = (AnswerIfc) answerArray.get(indexAnswer); if (a.getIsCorrect().booleanValue()) correctAnswers++; } } } } //log.debug("correctAnswers: " + correctAnswers); //check if there's wrong answer in the answer list, matrix survey question won't affect it, since the answer is always right, //so don't need to check the matrix survey question while (iterAnswer.hasNext()) { ItemGradingData data = (ItemGradingData) iterAnswer.next(); AnswerIfc answer = (AnswerIfc) publishedAnswerHash.get(data.getPublishedAnswerId()); if (item.getTypeId().equals(TypeIfc.FILL_IN_BLANK)) { GradingService gs = new GradingService(); boolean correctanswer = gs.getFIBResult( data, fibmap, item, publishedAnswerHash); if (!correctanswer){ haswronganswer =true; break; } } else if (item.getTypeId().equals(TypeIfc.FILL_IN_NUMERIC)) { GradingService gs = new GradingService(); try { boolean correctanswer = gs.getFINResult( data, item, publishedAnswerHash); if (!correctanswer){ haswronganswer =true; break; } } catch (FormatException e) { log.debug("should not come to here"); } } else if ((item.getTypeId().equals(TypeIfc.MULTIPLE_CORRECT) )||(item.getTypeId().equals(TypeIfc.MULTIPLE_CORRECT_SINGLE_SELECTION) )||(item.getTypeId().equals(TypeIfc.MATCHING) )){ if ((answer !=null) && (answer.getIsCorrect() == null || !answer.getIsCorrect().booleanValue())){ haswronganswer =true; break; } else if (answer !=null) { // for matching, if no selection has been made, answer = null. //we don't want to increment mcmc_match_counter if answer is null mcmc_match_counter++; } } else { // for other question types, tf, mcsc, mcmc and matching if ((answer !=null) && (answer.getIsCorrect() == null || !answer.getIsCorrect().booleanValue())){ haswronganswer =true; break; } } } if ((item.getTypeId().equals(TypeIfc.MULTIPLE_CORRECT) )|| (item.getTypeId().equals(TypeIfc.MATCHING) )){ if (mcmc_match_counter==correctAnswers){ haswronganswer=false; } else { haswronganswer=true; } } if (haswronganswer) { itemBean.setFeedback(item.getInCorrectItemFeedback()); } else { itemBean.setFeedback(item.getCorrectItemFeedback()); } } // Do we randomize answer list? boolean randomize = false; i = item.getItemMetaDataSet().iterator(); while (i.hasNext()) { ItemMetaDataIfc meta = (ItemMetaDataIfc) i.next(); if (meta.getLabel().equals(ItemMetaDataIfc.RANDOMIZE)) { if (meta.getEntry().equals("true")) { randomize = true; break; } } } ArrayList myanswers = new ArrayList(); ResourceLoader rb = null; // Generate the answer key String key = ""; Iterator key1 = item.getItemTextArraySorted().iterator(); int j = 0; while (key1.hasNext()) { j++; // We need to store the answers in an arraylist in case they're // randomized -- we assign labels here, and then step through // them again later, and we have to make sure the order is the // same each time. myanswers = new ArrayList(); // Start over each time so we don't // get duplicates. ItemTextIfc text = (ItemTextIfc) key1.next(); Iterator key2 = null; // Never randomize Fill-in-the-blank or Numeric Response, always randomize matching if (randomize && !(item.getTypeId().equals(TypeIfc.FILL_IN_BLANK)|| item.getTypeId().equals(TypeIfc.FILL_IN_NUMERIC) || item.getTypeId().equals(TypeIfc.MATRIX_CHOICES_SURVEY)) || item.getTypeId().equals(TypeIfc.CALCULATED_QUESTION) || // CALCULATED_QUESTION item.getTypeId().equals(TypeIfc.MATCHING)) { ArrayList shuffled = new ArrayList(); Iterator i1 = text.getAnswerArraySorted().iterator(); while (i1.hasNext()) { shuffled.add(i1.next()); // Randomize matching the same way for each } // Show the answers in the same order that student did. String agentString = ""; if (delivery.getActionMode() == DeliveryBean.GRADE_ASSESSMENT) { StudentScoresBean studentscorebean = (StudentScoresBean) ContextUtil .lookupBean("studentScores"); agentString = studentscorebean.getStudentId(); } else { agentString = getAgentString(); } Collections.shuffle(shuffled, new Random( (long) item.getText().hashCode() + (getAgentString() + "_" + item.getItemId().toString()).hashCode())); /* if (item.getTypeId().equals(TypeIfc.MATCHING)) { Collections.shuffle(shuffled, new Random( (long) item.getText().hashCode() + getAgentString().hashCode())); } else { Collections.shuffle(shuffled, new Random( (long) item.getText().hashCode() + getAgentString().hashCode())); } */ key2 = shuffled.iterator(); } else { key2 = text.getAnswerArraySorted().iterator(); } int k = 0; while (key2.hasNext()) { AnswerIfc answer = (AnswerIfc) key2.next(); // Don't save the answer if it has no text if ( (answer.getText() == null || answer.getText().trim().equals("")) && (item.getTypeId().equals(TypeIfc.MULTIPLE_CHOICE) || item.getTypeId().equals(TypeIfc.MULTIPLE_CORRECT) || item.getTypeId().equals(TypeIfc.MULTIPLE_CORRECT_SINGLE_SELECTION) || item.getTypeId().equals(TypeIfc.MULTIPLE_CHOICE_SURVEY) || item.getTypeId().equals(TypeIfc.MATRIX_CHOICES_SURVEY))) { // Ignore, it's a null answer } else { // Set the label and key if ((!item.getPartialCreditFlag() && item.getTypeId().equals(TypeIfc.MULTIPLE_CHOICE)) || item.getTypeId().equals(TypeIfc.MULTIPLE_CORRECT) || item.getTypeId().equals(TypeIfc.MULTIPLE_CORRECT_SINGLE_SELECTION) || item.getTypeId().equals(TypeIfc.MATCHING)) { answer.setLabel(Character.toString(alphabet.charAt(k++))); if (answer.getIsCorrect() != null && answer.getIsCorrect().booleanValue()) { String addition = ""; if (item.getTypeId().equals(TypeIfc.MATCHING)) { addition = Integer.toString(j) + ":"; } if ("".equals(key)) { key += addition + answer.getLabel(); } else { key += ", " + addition + answer.getLabel(); } } } //multiple choice partial credit: if (item.getTypeId().equals(TypeIfc.MULTIPLE_CHOICE) && item.getPartialCreditFlag()){ Double pc = Double.valueOf(answer.getPartialCredit()); if (pc == null) { pc = Double.valueOf(0d); } if(pc > 0){ if (rb == null) { rb = new ResourceLoader("org.sakaiproject.tool.assessment.bundle.DeliveryMessages"); } String correct = rb.getString("alt_correct"); if(("").equals(key)){ key = answer.getLabel() + " <span style='color: green'>(" + pc + "% " + correct + ")</span>"; }else{ key += ", " + answer.getLabel() + " <span style='color: green'>(" + pc + "% " + correct + ")</span>"; } } } if (item.getTypeId().equals(TypeIfc.TRUE_FALSE) && answer.getIsCorrect() != null && answer.getIsCorrect().booleanValue()) { if (rb == null) { rb = new ResourceLoader("org.sakaiproject.tool.assessment.bundle.DeliveryMessages"); } if (answer.getText().equalsIgnoreCase("true") || answer.getText().equalsIgnoreCase(rb.getString("true_msg"))) { key = rb.getString("true_msg"); } else { key = rb.getString("false_msg"); } } if (item.getTypeId().equals(TypeIfc.FILE_UPLOAD) || item.getTypeId().equals(TypeIfc.ESSAY_QUESTION) || item.getTypeId().equals(TypeIfc.AUDIO_RECORDING)) { key += answer.getText(); } if (item.getTypeId().equals(TypeIfc.FILL_IN_BLANK)||item.getTypeId().equals(TypeIfc.FILL_IN_NUMERIC)) { if ("".equals(key)) { key += answer.getText(); } else { key += ", " + answer.getText(); } } // CALCULATED_QUESTION if (item.getTypeId().equals(TypeIfc.CALCULATED_QUESTION)) { key = commaDelimtedCalcQuestionAnswers(item, delivery, itemBean); } //myanswers will get the answer even for matrix and multiple choices survey myanswers.add(answer); } } } itemBean.setKey(key); // Delete this itemBean.setShuffledAnswers(myanswers); // This creates the list of answers for an item ArrayList answers = new ArrayList(); if (item.getTypeId().equals(TypeIfc.MULTIPLE_CHOICE) || item.getTypeId().equals(TypeIfc.MULTIPLE_CORRECT) || item.getTypeId().equals(TypeIfc.MULTIPLE_CORRECT_SINGLE_SELECTION) || item.getTypeId().equals(TypeIfc.MULTIPLE_CHOICE_SURVEY) || item.getTypeId().equals(TypeIfc.TRUE_FALSE) || item.getTypeId().equals(TypeIfc.MATCHING)) { Iterator iter = myanswers.iterator(); while (iter.hasNext()) { SelectionBean selectionBean = new SelectionBean(); selectionBean.setItemContentsBean(itemBean); AnswerIfc answer = (AnswerIfc) iter.next(); selectionBean.setAnswer(answer); // It's saved lower case in the db -- this is a kludge if (item.getTypeId().equals(TypeIfc.TRUE_FALSE) && // True/False answer.getText().equals("true")) { answer.setText(rb.getString("true_msg")); } if (item.getTypeId().equals(TypeIfc.TRUE_FALSE) && // True/False answer.getText().equals("false")) { answer.setText(rb.getString("false_msg")); } String label = ""; if (answer.getLabel() == null) { answer.setLabel(""); // Delete this when everything works. } if (!answer.getLabel().equals("")) { label += answer.getLabel() + ". " + answer.getText(); } else { label = answer.getText(); // Set the response to true or false for each answer } selectionBean.setResponse(false); // do this for each answer of choice, why? Iterator iter1 = itemBean.getItemGradingDataArray().iterator(); while (iter1.hasNext()) { ItemGradingData data = (ItemGradingData) iter1.next(); AnswerIfc pubAnswer = (AnswerIfc) publishedAnswerHash. get(data.getPublishedAnswerId()); if (pubAnswer != null && (pubAnswer.equals(answer) || data.getPublishedAnswerId().equals(answer.getId()))) { selectionBean.setItemGradingData(data); selectionBean.setResponse(true); //<-- is this redundant? } } if (delivery.getFeedbackComponent() != null && delivery.getFeedback().equals("true") && delivery.getFeedbackComponent().getShowSelectionLevel()) { // If right answer, set feedback to correct, otherwise incorrect if (answer.getIsCorrect() == null) { selectionBean.setFeedback(answer.getGeneralAnswerFeedback()); } else if (selectionBean.getResponse() && answer.getIsCorrect().booleanValue() || !selectionBean.getResponse() && !answer.getIsCorrect().booleanValue()) { selectionBean.setFeedback(answer.getCorrectAnswerFeedback()); } else { selectionBean.setFeedback(answer.getInCorrectAnswerFeedback()); } } // Delete this String description = ""; if (delivery.getFeedback().equals("true") && delivery.getFeedbackComponent().getShowCorrectResponse() && answer.getIsCorrect() != null) { description = answer.getIsCorrect().toString(); // Delete this } SelectItem newItem = new SelectItem(answer.getId().toString(), label, description); if (item.getTypeId().equals(TypeIfc.TRUE_FALSE)) { answers.add(newItem); } else { answers.add(selectionBean); } } } // Delete this itemBean.setAnswers(answers); itemBean.setSelectionArray(answers); if (item.getTypeId().equals(TypeIfc.MATCHING)) // matching { populateMatching(item, itemBean, publishedAnswerHash); } else if (item.getTypeId().equals(TypeIfc.FILL_IN_BLANK)) // fill in the blank { populateFib(item, itemBean, publishedAnswerHash); } else if (item.getTypeId().equals(TypeIfc.FILL_IN_NUMERIC)) //numeric response { populateFin(item, itemBean, publishedAnswerHash); } else if (item.getTypeId().equals(TypeIfc.ESSAY_QUESTION)) { String responseText = itemBean.getResponseText(); // SAK-17021 // itemBean.setResponseText(FormattedText.convertFormattedTextToPlaintext(responseText)); itemBean.setResponseText(ContextUtil.stringWYSIWYG(responseText)); } else if (item.getTypeId().equals(TypeIfc.MATRIX_CHOICES_SURVEY)) { populateMatrixChoices(item, itemBean, publishedAnswerHash); } // CALCULATED_QUESTION else if (item.getTypeId().equals(TypeIfc.CALCULATED_QUESTION)) { populateCalculatedQuestion(item, itemBean, delivery); } return itemBean; } public void populateMatching(ItemDataIfc item, ItemContentsBean bean, HashMap publishedAnswerHash) { // used only for questions with distractors where the user has selected None of the Above final Long NONE_OF_THE_ABOVE = -1l; Iterator iter = item.getItemTextArraySorted().iterator(); int j = 1; ArrayList beans = new ArrayList(); ArrayList newAnswers = null; while (iter.hasNext()) { ItemTextIfc text = (ItemTextIfc) iter.next(); MatchingBean mbean = new MatchingBean(); newAnswers = new ArrayList(); mbean.setText(Integer.toString(j++) + ". " + text.getText()); mbean.setItemText(text); mbean.setItemContentsBean(bean); ArrayList choices = new ArrayList(); ArrayList shuffled = new ArrayList(); Iterator iter2 = text.getAnswerArraySorted().iterator(); while (iter2.hasNext()) { shuffled.add(iter2.next()); } Collections.shuffle(shuffled, new Random( (long) item.getText().hashCode() + (getAgentString() + "_" + item.getItemId().toString()).hashCode())); /* Collections.shuffle (shuffled, new Random( (long) item.getText().hashCode())); */ iter2 = shuffled.iterator(); int i = 0; ResourceLoader rb = null; if (rb == null) { rb = new ResourceLoader("org.sakaiproject.tool.assessment.bundle.DeliveryMessages"); } choices.add(new SelectItem("0", rb.getString("matching_select"), "")); // default value for choice while (iter2.hasNext()) { AnswerIfc answer = (AnswerIfc) iter2.next(); newAnswers.add(Character.toString(alphabet.charAt(i)) + ". " + answer.getText()); choices.add(new SelectItem(answer.getId().toString(), Character.toString(alphabet.charAt(i++)), "")); } GradingService gs = new GradingService(); if (gs.hasDistractors(item)) { choices.add(new SelectItem(NONE_OF_THE_ABOVE.toString(), "None of the Above", "")); } mbean.setChoices(choices); // Set the A/B/C... pulldown iter2 = bean.getItemGradingDataArray().iterator(); while (iter2.hasNext()) { ItemGradingData data = (ItemGradingData) iter2.next(); if (data.getPublishedItemTextId().equals(text.getId())) { // We found an existing grading data for this itemtext mbean.setItemGradingData(data); AnswerIfc pubAnswer = (AnswerIfc) publishedAnswerHash.get(data.getPublishedAnswerId()); if (pubAnswer != null) { mbean.setAnswer(pubAnswer); mbean.setResponse(data.getPublishedAnswerId().toString()); if (pubAnswer.getIsCorrect() != null && pubAnswer.getIsCorrect().booleanValue()) { mbean.setFeedback(pubAnswer.getCorrectAnswerFeedback()); mbean.setIsCorrect(true); } else { mbean.setFeedback(pubAnswer.getInCorrectAnswerFeedback()); mbean.setIsCorrect(false); } } else if (NONE_OF_THE_ABOVE.equals(data.getPublishedAnswerId())) { mbean.setResponse(data.getPublishedAnswerId().toString()); } break; } } beans.add(mbean); } bean.setMatchingArray(beans); bean.setAnswers(newAnswers); // Change the answers to just text } public void populateFib(ItemDataIfc item, ItemContentsBean bean, HashMap<Long, AnswerIfc> publishedAnswerHash) { // Only one text in FIB ItemTextIfc text = (ItemTextIfc) item.getItemTextArraySorted().toArray()[0]; ArrayList fibs = new ArrayList(); String alltext = text.getText(); ArrayList texts = extractFIBFINTextArray(alltext); int i = 0; Iterator iter = text.getAnswerArraySorted().iterator(); while (iter.hasNext()) { AnswerIfc answer = (AnswerIfc) iter.next(); FibBean fbean = new FibBean(); fbean.setItemContentsBean(bean); fbean.setAnswer(answer); if(texts.toArray().length>i) fbean.setText( (String) texts.toArray()[i++]); else fbean.setText(""); fbean.setHasInput(true); ArrayList datas = bean.getItemGradingDataArray(); if (datas == null || datas.isEmpty()) { fbean.setIsCorrect(false); } else { Iterator iter2 = datas.iterator(); while (iter2.hasNext()) { ItemGradingData data = (ItemGradingData) iter2.next(); if ((data.getPublishedAnswerId()!=null) && data.getPublishedAnswerId().equals(answer.getId())) { fbean.setItemGradingData(data); fbean.setResponse(FormattedText.convertFormattedTextToPlaintext(data.getAnswerText())); if (answer.getText() == null) { answer.setText(""); } if (data.getIsCorrect() == null) { GradingService gs = new GradingService(); HashMap<Long, Set<String>> fibmap = new HashMap<Long, Set<String>>(); fbean.setIsCorrect(gs.getFIBResult(data, fibmap, item, publishedAnswerHash)); } else { if (data.getIsCorrect().booleanValue()) { fbean.setIsCorrect(true); } else { fbean.setIsCorrect(false); } } } } } fibs.add(fbean); } FibBean fbean = new FibBean(); if(texts.toArray().length>i) fbean.setText( (String) texts.toArray()[i]); else fbean.setText(""); fbean.setHasInput(false); fibs.add(fbean); bean.setFibArray(fibs); } private static ArrayList extractFIBFINTextArray(String alltext) { ArrayList texts = new ArrayList(); while (alltext.indexOf("{}") > -1) { int alltextLeftIndex = alltext.indexOf("{}"); //int alltextRightIndex = alltext.indexOf("}"); String tmp = alltext.substring(0, alltextLeftIndex); alltext = alltext.substring(alltextLeftIndex + 2); texts.add(tmp); // there are no more "{}", exit loop. // why do we this check? will it ever come to here? if (alltextLeftIndex == -1) { break; } } texts.add(alltext); return texts; } /** * Tests that malformed FIB text does not create an excessive number of loops. * Quickie test, nice to have: refine, move to JUnit. * @param verbose * @return */ /* private static boolean testExtractFIBTextArray(boolean verbose) { boolean status = true; String[] testsuite = { "aaa{bbb}ccc{ddd}eee", // correct "aaa{bbb}ccc{", //incorrect "aaa{bbb}ccc}", //incorrect "aaa{bbb{ccc}ddd}eee" //incorrect }; ArrayList testResult; try { for (int i = 0; i < testsuite.length; i++) { testResult = extractFIBTextArray(testsuite[i]); if (verbose) { log.debug("Extracting: " + testsuite[i]); for (int j = 0; j < testResult.size(); j++) { log.debug("testResult.get(" + j + ")="+testResult.get(j)); } } if (testResult.size() > 10) { if (verbose) { log.debug("Extraction failed: exceeded reasonable size."); } return false; } } } catch (Exception ex) { if (verbose) { log.debug("Extraction failed: " + ex); } return false; } return status; } */ public void populateFin(ItemDataIfc item, ItemContentsBean bean, HashMap<Long, AnswerIfc> publishedAnswerHash) { // Only one text in FIN ItemTextIfc text = (ItemTextIfc) item.getItemTextArraySorted().toArray()[0]; ArrayList fins = new ArrayList(); String alltext = text.getText(); ArrayList texts = extractFIBFINTextArray(alltext); int i = 0; Iterator iter = text.getAnswerArraySorted().iterator(); while (iter.hasNext()) { AnswerIfc answer = (AnswerIfc) iter.next(); FinBean fbean = new FinBean(); fbean.setItemContentsBean(bean); fbean.setAnswer(answer); if(texts.toArray().length>i) fbean.setText( (String) texts.toArray()[i++]); else fbean.setText(""); fbean.setHasInput(true); ArrayList datas = bean.getItemGradingDataArray(); if (datas == null || datas.isEmpty()) { fbean.setIsCorrect(false); } else { Iterator iter2 = datas.iterator(); while (iter2.hasNext()) { ItemGradingData data = (ItemGradingData) iter2.next(); log.debug(" " + data.getPublishedAnswerId() + " = " + answer.getId()); if ((data.getPublishedAnswerId()!=null) && (data.getPublishedAnswerId().equals(answer.getId()))) { fbean.setItemGradingData(data); fbean.setResponse(FormattedText.convertFormattedTextToPlaintext(data.getAnswerText())); if (answer.getText() == null) { answer.setText(""); } if (data.getIsCorrect() == null) { GradingService gs = new GradingService(); HashMap<Long, Set<String>> fibmap = new HashMap<Long, Set<String>>(); fbean.setIsCorrect(gs.getFINResult(data, item, publishedAnswerHash)); } else { if (data.getIsCorrect().booleanValue()) { fbean.setIsCorrect(true); } else { fbean.setIsCorrect(false); } } } } } fins.add(fbean); } FinBean fbean = new FinBean(); if(texts.toArray().length>i) fbean.setText( (String) texts.toArray()[i]); else fbean.setText(""); fbean.setHasInput(false); fins.add(fbean); bean.setFinArray(fins); } public void populateMatrixChoices(ItemDataIfc item, ItemContentsBean bean, HashMap publishedAnswerHash){ ArrayList matrixArray = new ArrayList(); List<Integer> columnIndexList = new ArrayList<Integer>(); ArrayList itemTextArray = item.getItemTextArraySorted(); ArrayList answerArray = ((ItemTextIfc)itemTextArray.get(0)).getAnswerArraySorted(); MatrixSurveyBean mbean = null; List<String> stringList = new ArrayList<String>(); for(int i=0; i<answerArray.size(); i++){ String str = ((AnswerIfc) answerArray.get(i)).getText(); if(str!=null && str.trim().length()>0) { stringList.add(str); } } for (int k=0; k<stringList.size(); k++){ columnIndexList.add(Integer.valueOf(k)); } String [] columnChoices = stringList.toArray(new String[stringList.size()]); List<ItemTextIfc> iList = new ArrayList<ItemTextIfc>(); for (int i=0; i<itemTextArray.size(); i++) { String str = ((ItemTextIfc) itemTextArray.get(i)).getText(); if (str!=null && str.trim().length()>0) iList.add((ItemTextIfc)itemTextArray.get(i)); } for(int i=0; i<iList.size(); i++) { ItemTextIfc text = iList.get(i); ArrayList answers = ((ItemTextIfc)itemTextArray.get(i)).getAnswerArraySorted(); List<AnswerIfc> alist = new ArrayList<AnswerIfc>(); List<String> slist = new ArrayList<String>(); for(int j= 0; j<answers.size(); j++) { if ((AnswerIfc)answers.get(j) != null && !"".equals(((AnswerIfc)answers.get(j)).getText().trim())) { alist.add((AnswerIfc)answers.get(j)); slist.add(((AnswerIfc)answers.get(j)).getId().toString()); } } AnswerIfc [] answerIfcs =alist.toArray(new AnswerIfc[alist.size()]); String[] answerSid = slist.toArray(new String[slist.size()]); mbean = new MatrixSurveyBean(); mbean.setItemText(text); mbean.setItemContentsBean(bean); mbean.setAnswerArray(answerIfcs); mbean.setAnswerSid(answerSid); ArrayList itemGradingArray = bean.getItemGradingDataArray(); for (int k=0; k< itemGradingArray.size(); k++) { ItemGradingData data = (ItemGradingData)itemGradingArray.get(k); if((data.getPublishedItemTextId()).longValue() == text.getId().longValue()){ mbean.setItemGradingData(data); if (data.getPublishedAnswerId() != null) mbean.setResponseId(data.getPublishedAnswerId().toString()); break; } } matrixArray.add(mbean); } bean.setColumnArray(columnChoices); bean.setColumnIndexList(columnIndexList); bean.setMatrixArray(matrixArray); bean.setForceRanking(Boolean.parseBoolean(item.getItemMetaDataByLabel(ItemMetaDataIfc.FORCE_RANKING))); if (item.getItemMetaDataByLabel(ItemMetaDataIfc.MX_SURVEY_RELATIVE_WIDTH) != null) { bean.setRelativeWidth(Integer.parseInt(item.getItemMetaDataByLabel(ItemMetaDataIfc.MX_SURVEY_RELATIVE_WIDTH))); } bean.setAddComment(Boolean.parseBoolean(item.getItemMetaDataByLabel(ItemMetaDataIfc.ADD_COMMENT_MATRIX))); bean.setCommentField(item.getItemMetaDataByLabel(ItemMetaDataIfc.MX_SURVEY_QUESTION_COMMENTFIELD)); } /** * Tests that malformed FIN text does not create an excessive number of loops. * Quickie test, nice to have: refine, move to JUnit. * @param verbose * @return */ /* private static boolean testExtractFINTextArray(boolean verbose) { boolean status = true; String[] testsuite = { "aaa{bbb}ccc{ddd}eee", // correct "aaa{bbb}ccc{", //incorrect "aaa{bbb}ccc}", //incorrect "aaa{bbb{ccc}ddd}eee" //incorrect }; ArrayList testResult; try { for (int i = 0; i < testsuite.length; i++) { testResult = extractFINTextArray(testsuite[i]); if (verbose) { log.debug("Extracting: " + testsuite[i]); for (int j = 0; j < testResult.size(); j++) { log.debug("testResult.get(" + j + ")="+testResult.get(j)); } } if (testResult.size() > 10) { if (verbose) { log.debug("Extraction failed: exceeded reasonable size."); } return false; } } } catch (Exception ex) { if (verbose) { log.debug("Extraction failed: " + ex); } return false; } return status; } */ /* public static void main (String args[]) { boolean verbose = true; if (args.length>0 && "false".equals(args[0])) { verbose = false; } //log.debug("testExtractFIBTextArray result="+testExtractFIBTextArray(verbose));; } */ /** * CALCULATED_QUESTION * This method essentially will convert a CalculatedQuestion item which is initially structured * like a Matching item and reshape it into something more akin to a Fill-in-the-blank numeric * item. */ public void populateCalculatedQuestion(ItemDataIfc item, ItemContentsBean bean, DeliveryBean delivery) { long gradingId = determineCalcQGradingId(delivery); String agentId = determineCalcQAgentId(delivery, bean); HashMap<Integer, String> answersMap = new HashMap<Integer, String>(); GradingService service = new GradingService(); // texts is the display text that will show in the question. AnswersMap gets populated with // pairs such as key:x, value:42.0 List<String> texts = service.extractCalcQAnswersArray(answersMap, item, gradingId, agentId); String questionText = texts.get(0); ItemTextIfc text = (ItemTextIfc) item.getItemTextArraySorted().toArray()[0]; List<FinBean> fins = new ArrayList<FinBean>(); bean.setInstruction(questionText); // will be referenced in table of contents int numOfAnswers = answersMap.size(); int i = 0; List<AnswerIfc> calcQuestionEntities = text.getAnswerArraySorted(); // Here's where I had to do it a little messy, so I'll explain. The variable are like // matching pairs so they are stored as answers. But this question has real answers // too. So I recycle the answer object I stored the variables in again to represent // answers too. // I sort this list by answer id so that it will come back from the student in a // predictable order. Collections.sort(calcQuestionEntities, new Comparator<AnswerIfc>(){ public int compare(AnswerIfc a1, AnswerIfc a2) { return a1.getId().compareTo(a2.getId()); } }); Iterator<AnswerIfc> iter = calcQuestionEntities.iterator(); while (iter.hasNext()) { if (i == numOfAnswers) break; // AnswerArray holds the vars so there may be more than we need AnswerIfc answer = iter.next(); answer.setIsCorrect(true); FinBean fbean = new FinBean(); fbean.setItemContentsBean(bean); fbean.setAnswer(answer); if (texts.toArray().length>i) { fbean.setText( (String) texts.toArray()[i++]); } else { fbean.setText(""); } fbean.setHasInput(true); // input box ArrayList<ItemGradingData> datas = bean.getItemGradingDataArray(); if (datas == null || datas.isEmpty()) { fbean.setIsCorrect(false); } else { for (ItemGradingData data : datas) { if ((data.getPublishedAnswerId()!=null) && (data.getPublishedAnswerId().equals(answer.getId()))) { fbean.setItemGradingData(data); fbean.setResponse(FormattedText.convertFormattedTextToPlaintext(data.getAnswerText())); fbean.setIsCorrect(false); if (answer.getText() == null) { answer.setText(""); } StringTokenizer st2 = new StringTokenizer(answer.getText(), "|"); while (st2.hasMoreTokens()) { String nextT = st2.nextToken(); log.debug("nextT = " + nextT); // mark answer as correct if autoscore > 0 if (data.getAutoScore() != null && data.getAutoScore().doubleValue() > 0.0) { fbean.setIsCorrect(true); } } } } } fins.add(fbean); } FinBean fbean = new FinBean(); if(texts.toArray().length>i) fbean.setText( (String) texts.toArray()[i]); else fbean.setText(""); fbean.setHasInput(false); fins.add(fbean); bean.setFinArray((ArrayList) fins); } public String getAgentString(){ PersonBean person = (PersonBean) ContextUtil.lookupBean("person"); String agentString = person.getId(); //log.info("***agentString="+agentString); return agentString; } //added by daisy, used by DeliverBean.addMediaToItemGrading public void processAction(ActionEvent ae, boolean resetPageContents) throws AbortProcessingException{ this.resetPageContents = resetPageContents; processAction(null); } public String getPublishedAssessmentId(DeliveryBean delivery){ String id = ContextUtil.lookupParam("publishedId"); if (id == null || id.equals("") || id.equals("null")){ id = delivery.getAssessmentId(); } return id; } protected void clearElapsedTime(DeliveryBean delivery){ if (!delivery.isTimeRunning()) { delivery.setTimeElapse(null); } delivery.setTimeOutSubmission("false"); if (delivery.getTimeElapse() == null){ delivery.setTimeElapse("0"); } } protected void setFeedbackMode(DeliveryBean delivery){ int action = delivery.getActionMode(); String showfeedbacknow = ContextUtil.lookupParam("showfeedbacknow"); delivery.setFeedback("false"); delivery.setNoFeedback("false"); switch (action){ case 1: // take assessment case 2: // preview assessment case 5: // take assessment via url if (showfeedbacknow != null && showfeedbacknow.equals("true")) { delivery.setFeedback("true"); } break; case 3: // review assessment if (delivery.getFeedbackComponent()!=null && (delivery.getFeedbackComponent().getShowImmediate() || delivery.getFeedbackComponent().getShowOnSubmission() || (delivery.getFeedbackComponent().getShowDateFeedback()) && delivery.getSettings()!=null && delivery.getSettings().getFeedbackDate()!=null && delivery.getSettings().getFeedbackDate().before(new Date()))) { delivery.setFeedback("true"); } break; case 4: // grade assessment delivery.setFeedback("true"); break; default:break; } String nofeedback = ContextUtil.lookupParam("nofeedback"); if (nofeedback != null && nofeedback.equals("true")) { delivery.setNoFeedback("true"); } } public PublishedAssessmentFacade getPublishedAssessment(DeliveryBean delivery, String id){ PublishedAssessmentFacade publishedAssessment = null; if (delivery.getPublishedAssessment() != null && delivery.getPublishedAssessment().getPublishedAssessmentId().toString(). equals(id)) { publishedAssessment = delivery.getPublishedAssessment(); } else { try{ publishedAssessment = (new PublishedAssessmentService()).getPublishedAssessment(id); delivery.setPublishedAssessment(publishedAssessment); } catch(Exception e){ log.warn(e.getMessage()); } } return publishedAssessment; } public void setShowStudentScore(DeliveryBean delivery, PublishedAssessmentFacade publishedAssessment){ if (Boolean.TRUE.equals( publishedAssessment.getAssessmentFeedback().getShowStudentScore())) { if (delivery.getFeedbackComponent()!=null && delivery.getFeedbackComponent().getShowDateFeedback() && !delivery.getFeedbackOnDate()) delivery.setShowStudentScore(false); else delivery.setShowStudentScore(true); } else { delivery.setShowStudentScore(false); } } public void setShowStudentQuestionScore(DeliveryBean delivery, PublishedAssessmentFacade publishedAssessment){ int action = delivery.getActionMode(); // Score should always be shown in grading flow if (DeliveryBean.GRADE_ASSESSMENT == action) { delivery.setShowStudentQuestionScore(true); } else if (Boolean.TRUE.equals(publishedAssessment.getAssessmentFeedback().getShowStudentQuestionScore())) { if (delivery.getFeedbackComponent()!=null && ((delivery.getFeedbackComponent().getShowDateFeedback() && !delivery.getFeedbackOnDate()) || ((DeliveryBean.TAKE_ASSESSMENT == action || DeliveryBean.TAKE_ASSESSMENT_VIA_URL == action) && delivery.getFeedbackComponent().getShowOnSubmission()))) delivery.setShowStudentQuestionScore(false); else delivery.setShowStudentQuestionScore(true); } else { delivery.setShowStudentQuestionScore(false); } } private void setDisplayByAssessment(DeliveryBean delivery){ delivery.getSettings().setFormatByAssessment(true); delivery.getSettings().setFormatByPart(false); delivery.getSettings().setFormatByQuestion(false); } protected void setDeliverySettings(DeliveryBean delivery, PublishedAssessmentFacade publishedAssessment){ if (delivery.getSettings() == null){ BeginDeliveryActionListener listener = new BeginDeliveryActionListener(); listener.populateBeanFromPub(delivery, publishedAssessment); } } public void overloadItemData(DeliveryBean delivery, HashMap itemGradingHash, PublishedAssessmentFacade publishedAssessment){ // We're going to overload itemGradingHash with the sequence in case // renumbering is turned off. itemGradingHash.put("sequence", Long.valueOf(0)); long items = 0; int sequenceno = 1; if (publishedAssessment != null && publishedAssessment.getSectionArraySorted() != null) { Iterator i1 = publishedAssessment.getSectionArraySorted().iterator(); while (i1.hasNext()) { SectionDataIfc section = (SectionDataIfc) i1.next(); Iterator i2 = null; ArrayList itemlist = section.getItemArray(); long seed = 0; if (delivery.getActionMode()==DeliveryBean.GRADE_ASSESSMENT) { StudentScoresBean studentscorebean = (StudentScoresBean) ContextUtil.lookupBean("studentScores"); seed = getSeed(section, delivery, (long) studentscorebean.getStudentId().hashCode()); } else { seed = getSeed(section, delivery, (long) AgentFacade.getAgentString().hashCode()); } ArrayList sortedlist = getItemArraySortedWithRandom(section, itemlist, seed); i2 = sortedlist.iterator(); while (i2.hasNext()) { items = items + 1; // bug 464 ItemDataIfc item = (ItemDataIfc) i2.next(); itemGradingHash.put("sequence" + item.getItemId().toString(), Integer.valueOf(sequenceno++)); } } } itemGradingHash.put("items", Long.valueOf(items)); } private void setGraderComment(DeliveryBean delivery){ if (delivery.getAssessmentGrading() != null) { delivery.setGraderComment (delivery.getAssessmentGrading().getComments()); } else { delivery.setGraderComment(null); } } protected void setTimer(DeliveryBean delivery, PublishedAssessmentFacade publishedAssessment, boolean fromBeginAssessment, boolean isFirstTimeBegin){ // i hope to use the property timedAssessment but it appears that this property // is not recorded properly in DB - daisyf int timeLimit = 0; AssessmentAccessControlIfc control = publishedAssessment.getAssessmentAccessControl(); AssessmentGradingData ag = delivery.getAssessmentGrading(); delivery.setBeginTime(ag.getAttemptDate()); if (delivery.getHasTimeLimit() && control != null && control.getTimeLimit()!=null) { if (fromBeginAssessment) { timeLimit = Integer.parseInt(delivery.updateTimeLimit(publishedAssessment.getAssessmentAccessControl().getTimeLimit().toString())); } else { if (delivery.getTimeLimit() != null) { timeLimit = Integer.parseInt(delivery.getTimeLimit()); } } } if (timeLimit==0) delivery.setTimeRunning(false); else{ delivery.setTimeRunning(true); delivery.setTimeLimit(timeLimit+""); //if assessment is half done, load setting saved in DB, // else set time elapsed as 0 //AssessmentGradingData ag = delivery.getAssessmentGrading(); //delivery.setBeginTime(ag.getAttemptDate()); if (delivery.isTimeRunning() && delivery.getBeginAssessment()){ //int timeElapsed = Math.round((double)((new Date()).getTime() - ag.getAttemptDate().getTime())/1000.0d); //in sec //delivery.setTimeElapse(String.valueOf(timeElapsed)); /* if (ag.getTimeElapsed() != null){ delivery.setTimeElapse(ag.getTimeElapsed().toString()); } else{ // this is a new timed assessment delivery.setTimeElapse("0"); } */ queueTimedAssessment(delivery, timeLimit, fromBeginAssessment, publishedAssessment, isFirstTimeBegin); delivery.setBeginAssessment(false); } else{ // in midst of assessment taking, sync it with timedAG TimedAssessmentQueue queue = TimedAssessmentQueue.getInstance(); TimedAssessmentGradingModel timedAG = queue.get(ag.getAssessmentGradingId()); if (timedAG != null) syncTimeElapsedWithServer(timedAG, delivery, isFirstTimeBegin); } if (delivery.getLastTimer()==0){ delivery.setLastTimer((new Date()).getTime()); //set the time when the user click Begin Assessment } } } private void queueTimedAssessment(DeliveryBean delivery, int timeLimit, boolean fromBeginAssessment, PublishedAssessmentFacade publishedAssessment, boolean isFirstTimeBegin){ AssessmentGradingData ag = delivery.getAssessmentGrading(); TimedAssessmentQueue queue = TimedAssessmentQueue.getInstance(); TimedAssessmentGradingModel timedAG = queue.get(ag.getAssessmentGradingId()); if (isFirstTimeBegin){ timedAG = new TimedAssessmentGradingModel(ag.getAssessmentGradingId(), timeLimit, timeLimit, new Date(), new Date(), // need modify later false, getTimerId(delivery), publishedAssessment); queue.add(timedAG); delivery.setTimeElapse("0"); //log.debug("***0. queue="+queue); //log.debug("***1. put timedAG in queue, timedAG="+timedAG); } else{ if (timedAG == null){ int timeElapsed = Math.round((new Date().getTime() - ag.getAttemptDate().getTime())/1000.0f); timedAG = new TimedAssessmentGradingModel(ag.getAssessmentGradingId(), timeLimit, timeLimit - timeElapsed, new Date(), new Date(), false, getTimerId(delivery), publishedAssessment); queue.add(timedAG); delivery.setTimeElapse(String.valueOf(timeElapsed)); } else { // if timedAG exists && beginAssessment==true, this is dodgy. It means that // users may have exited the assessment via unusual mean (e.g. clicking at // a leftnav bar link). In order to return to the assessment that he is taking, // he must go through the beginAssessment screen again, hence, beginAssessment is set // to true again. In this case, we need to sync up the JScript time with the server time // We need to correct 2 settings based on timedAG: delivery.timeElapse syncTimeElapsedWithServer(timedAG, delivery, false); } } } private void syncTimeElapsedWithServer(TimedAssessmentGradingModel timedAG, DeliveryBean delivery, boolean isFirstTimeBegin){ AssessmentGradingData ag = delivery.getAssessmentGrading(); //boolean zeroTimeElapsed = false; /* boolean acceptLateSubmission = AssessmentAccessControlIfc.ACCEPT_LATE_SUBMISSION.equals(delivery.getPublishedAssessment().getAssessmentAccessControl().getLateHandling()); int timeLimit = Integer.parseInt(delivery.getPublishedAssessment().getAssessmentAccessControl().getTimeLimit().toString()); // due date if (delivery.getDueDate() != null && !acceptLateSubmission) { int timeBeforeDue = Math.round((double)(delivery.getDueDate().getTime() - (new Date()).getTime())/1000.0d); //in sec if (timeBeforeDue < timeLimit && fromBeginAssessment) { delivery.setTimeElapse("0"); timedAG.setBeginDate(new Date()); return; } } // retract date if (delivery.getRetractDate() != null) { int timeBeforeRetract = Math.round((double)(delivery.getRetractDate().getTime() - (new Date()).getTime())/1000.0d); //in sec if (timeBeforeRetract < timeLimit && fromBeginAssessment) { delivery.setTimeElapse("0"); timedAG.setBeginDate(new Date()); return; } } */ // this is to cover the scenerio when user took an assessment, Save & Exit, Then returned at a // later time, we need to account for the time taht he used before int timeElapsed = Math.round((new Date().getTime() - ag.getAttemptDate().getTime())/1000.0f); log.debug("***setTimeElapsed="+timeElapsed); ag.setTimeElapsed(Integer.valueOf(timeElapsed)); // not sure why isLate lost its value, so setting it again here ag.setIsLate(Boolean.FALSE); GradingService gradingService = new GradingService(); gradingService.saveOrUpdateAssessmentGradingOnly(ag); delivery.setTimeElapse(ag.getTimeElapsed().toString()); } protected AssessmentGradingData createAssessmentGrading( PublishedAssessmentFacade publishedAssessment){ AssessmentGradingData adata = new AssessmentGradingData(); adata.setAgentId(getAgentString()); adata.setPublishedAssessmentId(publishedAssessment.getPublishedAssessmentId()); adata.setForGrade(Boolean.FALSE); adata.setAttemptDate(new Date()); adata.setIsLate(Boolean.FALSE); adata.setStatus(Integer.valueOf(0)); adata.setTotalOverrideScore(Double.valueOf(0)); adata.setTimeElapsed(Integer.valueOf("0")); GradingService gradingService = new GradingService(); gradingService.saveOrUpdateAssessmentGrading(adata); return adata; } protected void setAttemptDateIfNull(AssessmentGradingData ag){ if (ag.getAttemptDate() == null) { ag.setAttemptDate(new Date()); GradingService gradingService = new GradingService(); gradingService.saveOrUpdateAssessmentGrading(ag); } } /* this method takes the list returned from the data/dao class, and checks for part type and returns a sorted list of items. If part type is not random then return the original list */ private ArrayList getItemArraySortedWithRandom(SectionDataIfc part, ArrayList list, long seed) { Integer numberToBeDrawn= null; if ((part.getSectionMetaDataByLabel(SectionDataIfc.AUTHOR_TYPE)!=null) && (part.getSectionMetaDataByLabel(SectionDataIfc.AUTHOR_TYPE).equals(SectionDataIfc.RANDOM_DRAW_FROM_QUESTIONPOOL.toString()))) { // same ordering for each student ArrayList randomsample = new ArrayList(); Collections.shuffle(list, new Random(seed)); if (part.getSectionMetaDataByLabel(SectionDataIfc.NUM_QUESTIONS_DRAWN) !=null ) { numberToBeDrawn= Integer.valueOf(part.getSectionMetaDataByLabel(SectionDataIfc.NUM_QUESTIONS_DRAWN)); } int samplesize = 0; if (numberToBeDrawn != null) { samplesize = numberToBeDrawn.intValue(); } for (int i=0; i<samplesize; i++){ randomsample.add(list.get(i)); } return randomsample; } else if((part.getSectionMetaDataByLabel(SectionDataIfc.QUESTIONS_ORDERING)!=null ) && (part.getSectionMetaDataByLabel(SectionDataIfc.QUESTIONS_ORDERING).equals(SectionDataIfc.RANDOM_WITHIN_PART.toString())) ){ // same ordering for each student Collections.shuffle(list, new Random(seed)); return list; } else { Collections.sort(list); return list; } } // SAK-6990: if DeliveryActionListener is called by beginAssessment.jsp, a timerId // is assigned to timedAssessment that we need to attach to TimedAssessmentGradingModel private String getTimerId(DeliveryBean delivery){ String timerId = (String) ContextUtil.lookupParam("timerId"); log.debug("***timerId="+timerId); return timerId; } private long getSeed(SectionDataIfc sectionData, DeliveryBean delivery, long userSeed) { long seed = userSeed; log.debug("input seed = " + seed); if (sectionData.getSectionMetaDataByLabel(SectionDataIfc.RANDOMIZATION_TYPE) != null && sectionData.getSectionMetaDataByLabel(SectionDataIfc.RANDOMIZATION_TYPE).equals(SectionDataIfc.PER_SUBMISSION)) { Long id = delivery.getAssessmentGradingId(); log.debug("assessmentGradingId = " + id); if (delivery.getActionMode() == 2) { // this happens during preview assessment log.debug("preview assessment: seed = " + seed); } else if (id != null) { seed = (long) (id.toString() + "_" + sectionData.getSectionId().toString()).hashCode(); log.debug("seed = " + seed); } else { log.error("assessmentGradingId is null"); } } return seed; } // Set the published assessment status here // If it is retracted for edit, redirect to an error page protected void setStatus(DeliveryBean delivery, PublishedAssessmentService pubService, Long publishedAssessmentId) { Integer status = pubService.getPublishedAssessmentStatus(publishedAssessmentId); delivery.getPublishedAssessment().setStatus(status); } /** * CALCULATED_QUESTION * This returns the comma delimted answer key for display such as "42.1,23.19" */ private String commaDelimtedCalcQuestionAnswers(ItemDataIfc item, DeliveryBean delivery, ItemContentsBean itemBean) { long gradingId = determineCalcQGradingId(delivery); String agentId = determineCalcQAgentId(delivery, itemBean); String keysString = ""; GradingService service = new GradingService(); HashMap<Integer, String> answersMap = new HashMap<Integer, String>(); service.extractCalcQAnswersArray(answersMap, item, gradingId, agentId); // return value not used, answersMap is populated int answerSequence = 1; // this corresponds to the sequence value assigned in extractCalcQAnswersArray() while(answerSequence <= answersMap.size()) { String answer = (String)answersMap.get(answerSequence); answer = answer.substring(0, answer.indexOf("|")); // cut off extra data e.g. "|2,3" keysString = keysString.concat(answer + ","); answerSequence++; } if (keysString.length() > 2) { keysString = keysString.substring(0, keysString.length()-1); // truncating the comma on the end } return keysString; } /** * CALCULATED_QUESTION * We need the agentIds in order to properly set the pseudorandom seed * for calculated questions. */ private String determineCalcQAgentId(DeliveryBean delivery, ItemContentsBean itemBean) { String agentId = ""; if (delivery.getAssessmentGradingId() != null) { //not a preview if (delivery.getAssessmentGrading() != null) { agentId = delivery.getAssessmentGrading().getAgentId(); } else { // Instructor or TA is accessing the data if (itemBean.getItemGradingDataArray().size() == 0) { return "error"; } Iterator iterForAgent = itemBean.getItemGradingDataArray().iterator(); ItemGradingData dataForAgent = (ItemGradingData) iterForAgent.next(); agentId = dataForAgent.getAgentId(); } } else { // preview // give the instructor a random value each time for this agentId = "instructor_preview"; // doesn't really matter for preview } return agentId; } /** * CALCULATED_QUESTION * We need the gradingIds in order to properly set the pseudorandom seed * for calculated questions. */ private long determineCalcQGradingId(DeliveryBean delivery) { long gradingId = 0; if (delivery.getAssessmentGradingId() != null) { //not a preview gradingId = delivery.getAssessmentGradingId(); } else { // preview // give the instructor a random value each time for this gradingId = previewGradingId; } return gradingId; } protected LRS_Statement getStatementForTakeAssessment(LRS_Actor actor, Event event, String assessmentName) { String url = ServerConfigurationService.getPortalUrl(); LRS_Verb verb = new LRS_Verb(SAKAI_VERB.attempted); LRS_Object lrsObject = new LRS_Object(url + "/assessment", "attempted-assessment"); HashMap<String, String> nameMap = new HashMap<String, String>(); nameMap.put("en-US", "User attempted assessment"); lrsObject.setActivityName(nameMap); HashMap<String, String> descMap = new HashMap<String, String>(); descMap.put("en-US", "User attempted assessment: " + assessmentName); lrsObject.setDescription(descMap); return new LRS_Statement(actor, verb, lrsObject); } }