Java tutorial
/** * Copyright 2005 Sakai Foundation Licensed under the * Educational Community License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.osedu.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing * permissions and limitations under the License. */ package org.sakaiproject.evaluation.tool.producers; import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.evaluation.constant.EvalConstants; import org.sakaiproject.evaluation.logic.EvalAuthoringService; import org.sakaiproject.evaluation.logic.EvalCommonLogic; import org.sakaiproject.evaluation.logic.EvalEvaluationService; import org.sakaiproject.evaluation.logic.EvalSettings; import org.sakaiproject.evaluation.logic.exceptions.ResponseSaveException; import org.sakaiproject.evaluation.logic.externals.EvalExternalLogic; import org.sakaiproject.evaluation.logic.externals.ExternalHierarchyLogic; import org.sakaiproject.evaluation.logic.model.EvalGroup; import org.sakaiproject.evaluation.logic.model.EvalUser; import org.sakaiproject.evaluation.model.EvalAnswer; import org.sakaiproject.evaluation.model.EvalAssignGroup; import org.sakaiproject.evaluation.model.EvalAssignUser; import org.sakaiproject.evaluation.model.EvalEvaluation; import org.sakaiproject.evaluation.model.EvalResponse; import org.sakaiproject.evaluation.model.EvalTemplateItem; import org.sakaiproject.evaluation.tool.LocalResponsesLogic; import org.sakaiproject.evaluation.tool.locators.ResponseAnswersBeanLocator; import org.sakaiproject.evaluation.tool.renderers.ItemRenderer; import org.sakaiproject.evaluation.tool.utils.RenderingUtils; import org.sakaiproject.evaluation.tool.viewparams.EvalCategoryViewParameters; import org.sakaiproject.evaluation.tool.viewparams.EvalViewParameters; import org.sakaiproject.evaluation.utils.EvalUtils; import org.sakaiproject.evaluation.utils.TemplateItemDataList; import org.sakaiproject.evaluation.utils.TemplateItemDataList.DataTemplateItem; import org.sakaiproject.evaluation.utils.TemplateItemDataList.HierarchyNodeGroup; import org.sakaiproject.evaluation.utils.TemplateItemDataList.TemplateItemGroup; import org.sakaiproject.evaluation.utils.TemplateItemUtils; import uk.org.ponder.messageutil.MessageLocator; import uk.org.ponder.messageutil.TargettedMessage; import uk.org.ponder.messageutil.TargettedMessageList; import uk.org.ponder.rsf.components.ELReference; import uk.org.ponder.rsf.components.UIBranchContainer; import uk.org.ponder.rsf.components.UICommand; import uk.org.ponder.rsf.components.UIContainer; import uk.org.ponder.rsf.components.UIELBinding; import uk.org.ponder.rsf.components.UIForm; import uk.org.ponder.rsf.components.UIInternalLink; import uk.org.ponder.rsf.components.UIMessage; import uk.org.ponder.rsf.components.UIOutput; import uk.org.ponder.rsf.components.UIOutputMany; import uk.org.ponder.rsf.components.UISelect; import uk.org.ponder.rsf.components.UISelectChoice; import uk.org.ponder.rsf.components.UISelectLabel; import uk.org.ponder.rsf.components.UIVerbatim; import uk.org.ponder.rsf.components.decorators.DecoratorList; import uk.org.ponder.rsf.components.decorators.UICSSDecorator; import uk.org.ponder.rsf.components.decorators.UIFreeAttributeDecorator; import uk.org.ponder.rsf.components.decorators.UIIDStrategyDecorator; import uk.org.ponder.rsf.components.decorators.UIStyleDecorator; import uk.org.ponder.rsf.flow.ARIResult; import uk.org.ponder.rsf.flow.ActionResultInterceptor; import uk.org.ponder.rsf.flow.jsfnav.NavigationCase; import uk.org.ponder.rsf.flow.jsfnav.NavigationCaseReporter; import uk.org.ponder.rsf.view.ComponentChecker; import uk.org.ponder.rsf.viewstate.SimpleViewParameters; import uk.org.ponder.rsf.viewstate.ViewParameters; import uk.org.ponder.rsf.viewstate.ViewParamsReporter; /** * This page is for a user with take evaluation permission to fill and submit the evaluation * * @author Aaron Zeckoski (aaronz@vt.edu) */ public class TakeEvalProducer extends EvalCommonProducer implements ViewParamsReporter, NavigationCaseReporter, ActionResultInterceptor { private static final String SELECT_KEY_ASSISTANT = "assistant"; private static final String SELECT_KEY_INSTRUCTOR = "instructor"; private static final Log LOG = LogFactory.getLog(TakeEvalProducer.class); public static final String VIEW_ID = "take_eval"; public String getViewID() { return VIEW_ID; } private EvalCommonLogic commonLogic; public void setCommonLogic(EvalCommonLogic commonLogic) { this.commonLogic = commonLogic; } private EvalAuthoringService authoringService; public void setAuthoringService(EvalAuthoringService authoringService) { this.authoringService = authoringService; } private EvalEvaluationService evaluationService; public void setEvaluationService(EvalEvaluationService evaluationService) { this.evaluationService = evaluationService; } ItemRenderer itemRenderer; public void setItemRenderer(ItemRenderer itemRenderer) { this.itemRenderer = itemRenderer; } private LocalResponsesLogic localResponsesLogic; public void setLocalResponsesLogic(LocalResponsesLogic localResponsesLogic) { this.localResponsesLogic = localResponsesLogic; } private ExternalHierarchyLogic hierarchyLogic; public void setExternalHierarchyLogic(ExternalHierarchyLogic logic) { this.hierarchyLogic = logic; } private EvalSettings evalSettings; public void setEvalSettings(EvalSettings evalSettings) { this.evalSettings = evalSettings; } private TargettedMessageList messages; public void setMessages(TargettedMessageList messages) { this.messages = messages; } private Locale locale; public void setLocale(Locale locale) { this.locale = locale; } private MessageLocator messageLocator; public void setMessageLocator(MessageLocator messageLocator) { this.messageLocator = messageLocator; } private HttpServletResponse httpServletResponse; public void setHttpServletResponse(HttpServletResponse httpServletResponse) { this.httpServletResponse = httpServletResponse; } private RenderingUtils renderingUtils; public void setRenderingUtils(RenderingUtils renderingUtils) { this.renderingUtils = renderingUtils; } private EvalExternalLogic evalExternalLogic; public void setEvalExternalLogic(EvalExternalLogic evalExternalLogic) { this.evalExternalLogic = evalExternalLogic; } Long responseId; int displayNumber = 1; int renderedItemCount = 0; /** * Map of key to Answers for the current response<br/> * key = templateItemId + answer.associatedType + answer.associatedId */ Map<String, EvalAnswer> answerMap = new HashMap<>(); Map<String, String[]> savedSelections = new HashMap<>(); /** * If this is a re-opened response this will contain an {@link EvalResponse} */ EvalResponse response; /* (non-Javadoc) * @see uk.org.ponder.rsf.view.ComponentProducer#fillComponents(uk.org.ponder.rsf.components.UIContainer, uk.org.ponder.rsf.viewstate.ViewParameters, uk.org.ponder.rsf.view.ComponentChecker) */ public void fill(UIContainer tofill, ViewParameters viewparams, ComponentChecker checker) { // force the headers to expire this - http://jira.sakaiproject.org/jira/browse/EVALSYS-621 RenderingUtils.setNoCacheHeaders(httpServletResponse); boolean canAccess = false; // can a user access this evaluation boolean userCanAccess = false; // can THIS user take this evaluation boolean isUserSitePublished = true; //is the users' valid group(s) published? At least one group must be String currentUserId = commonLogic.getCurrentUserId(); // set the session timeout to 5 hours evalExternalLogic.setSessionTimeout(18000); // use a date which is related to the current users locale DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, locale); UIMessage.make(tofill, "page-title", "takeeval.page.title"); // get passed in get params EvalViewParameters evalTakeViewParams = (EvalViewParameters) viewparams; Long evaluationId = evalTakeViewParams.evaluationId; if (evaluationId == null) { // redirect over to the main view maybe?? (not sure how to do this in RSF) LOG.debug("User (" + currentUserId + ") cannot take evaluation, eval id is not set"); throw new IllegalArgumentException( "Invalid evaluationId: id must be set and cannot be null, cannot load evaluation"); } String evalGroupId = evalTakeViewParams.evalGroupId; responseId = evalTakeViewParams.responseId; // get the evaluation based on the passed in VPs EvalEvaluation eval = evaluationService.getEvaluationById(evaluationId); if (eval == null) { throw new IllegalArgumentException( "Invalid evaluationId (" + evaluationId + "), cannot load evaluation"); } if (!evalTakeViewParams.external) { UIBranchContainer navTool = UIBranchContainer.make(tofill, "navIntraTool:"); UIInternalLink.make(navTool, "summary-link", UIMessage.make("summary.page.title"), new SimpleViewParameters(SummaryProducer.VIEW_ID)); } UIMessage.make(tofill, "eval-title-header", "takeeval.eval.title.header"); UIOutput.make(tofill, "evalTitle", eval.getTitle()); /* check the states of the evaluation first to give the user a tip that this eval is not takeable, * also avoids wasting time checking permissions when the evaluation certainly is closed, * also allows us to give the user a nice custom message */ String evalState = evaluationService.returnAndFixEvalState(eval, true); // make sure state is up to date if (EvalUtils.checkStateBefore(evalState, EvalConstants.EVALUATION_STATE_ACTIVE, false)) { String dueDate = "--------"; if (eval.getDueDate() != null) { dueDate = df.format(eval.getDueDate()); } UIMessage.make(tofill, "eval-cannot-take-message", "takeeval.eval.not.open", new String[] { df.format(eval.getStartDate()), dueDate }); LOG.info("User (" + currentUserId + ") cannot take evaluation yet, not open until: " + eval.getStartDate()); } else if (EvalUtils.checkStateAfter(evalState, EvalConstants.EVALUATION_STATE_CLOSED, true)) { UIMessage.make(tofill, "eval-cannot-take-message", "takeeval.eval.closed", new String[] { df.format(eval.getDueDate()) }); LOG.info( "User (" + currentUserId + ") cannot take evaluation anymore, closed on: " + eval.getDueDate()); } else { // eval state is possible to take eval canAccess = true; } List<EvalGroup> validGroups = new ArrayList<>(); // stores EvalGroup objects if (canAccess) { if (LOG.isDebugEnabled()) { LOG.debug("User (" + currentUserId + ") can take evalution (" + evaluationId + ")"); } // eval is accessible so check user can take it if (evalGroupId != null) { // there was an eval group passed in so make sure things are ok if (evaluationService.canTakeEvaluation(currentUserId, evaluationId, evalGroupId)) { userCanAccess = true; } else { isUserSitePublished = false; } } // select the first eval group the current user can take evaluation in, // also store the total number so we can give the user a list to choose from if there are more than one Map<Long, List<EvalAssignGroup>> m = evaluationService .getAssignGroupsForEvals(new Long[] { evaluationId }, true, null); if (!commonLogic.isUserAnonymous(currentUserId) && commonLogic.isUserAdmin(currentUserId)) { // special case, the super admin can always access userCanAccess = true; List<EvalAssignGroup> assignGroups = m.get(evaluationId); for (int i = 0; i < assignGroups.size(); i++) { EvalAssignGroup assignGroup = assignGroups.get(i); if (evalGroupId == null) { // set the evalGroupId to the first valid group if unset evalGroupId = assignGroup.getEvalGroupId(); } validGroups.add(commonLogic.makeEvalGroupObject(assignGroup.getEvalGroupId())); } } else { EvalGroup[] evalGroups; if (EvalConstants.EVALUATION_AUTHCONTROL_NONE.equals(eval.getAuthControl())) { // anonymous eval allows any group to be evaluated List<EvalAssignGroup> assignGroups = m.get(evaluationId); evalGroups = new EvalGroup[assignGroups.size()]; for (int i = 0; i < assignGroups.size(); i++) { EvalAssignGroup assignGroup = assignGroups.get(i); evalGroups[i] = commonLogic.makeEvalGroupObject(assignGroup.getEvalGroupId()); } } else { List<EvalAssignUser> userAssignments = evaluationService.getParticipantsForEval(evaluationId, currentUserId, null, EvalAssignUser.TYPE_EVALUATOR, null, null, null); Set<String> evalGroupIds = EvalUtils.getGroupIdsFromUserAssignments(userAssignments); List<EvalGroup> groups = EvalUtils.makeGroupsFromGroupsIds(evalGroupIds, commonLogic); evalGroups = EvalUtils.getGroupsInCommon(groups, m.get(evaluationId)); } for (EvalGroup group : evalGroups) { if (evaluationService.canTakeEvaluation(currentUserId, evaluationId, group.evalGroupId)) { if (evalGroupId == null) { // set the evalGroupId to the first valid group if unset evalGroupId = group.evalGroupId; userCanAccess = true; } validGroups.add(commonLogic.makeEvalGroupObject(group.evalGroupId)); } } isUserSitePublished = userCanAccess; } if (!isUserSitePublished) { userCanAccess = false; UIMessage.make(tofill, "eval-cannot-take-message", "takeeval.eval.site.notpublished"); LOG.info( "User (" + currentUserId + ") cannot take evaluation because his site(s) are unpublished."); } else if (userCanAccess) { // check if we had a failure during a previous submit and get the missingKeys out if there are some Set<String> missingKeys = new HashSet<>(); if (messages.isError() && messages.size() > 0) { for (int i = 0; i < messages.size(); i++) { TargettedMessage message = messages.messageAt(i); Exception e = message.exception; if (e instanceof ResponseSaveException) { ResponseSaveException rse = (ResponseSaveException) e; if (rse.missingItemAnswerKeys != null && rse.missingItemAnswerKeys.length > 0) { missingKeys.addAll(Arrays.asList(rse.missingItemAnswerKeys)); } break; } } } // load up the response if this user has one already if (responseId == null) { response = evaluationService.getResponseForUserAndGroup(evaluationId, currentUserId, evalGroupId); if (response == null) { if (LOG.isDebugEnabled()) { LOG.debug("User (" + currentUserId + ") has no previous response for eval (" + evaluationId + ")"); } // create the initial response if there is not one // EVALSYS-360 because of a hibernate issue this will not work, do a binding instead -AZ //responseId = localResponsesLogic.createResponse(evaluationId, currentUserId, evalGroupId); } else { responseId = response.getId(); if (LOG.isDebugEnabled()) { LOG.debug("User (" + currentUserId + ") has previous response (" + responseId + ") in eval (" + evaluationId + ")"); } } } if (responseId != null) { // load up the previous responses for this user (no need to attempt to load if the response is new, there will be no answers yet) answerMap = localResponsesLogic.getAnswersMapByTempItemAndAssociated(responseId); if (LOG.isDebugEnabled()) { LOG.debug("User (" + currentUserId + "), eval (" + evaluationId + "), previous answers map: " + answerMap); } } // show the switch group selection and form if there are other valid groups for this user if (validGroups.size() > 1) { String[] values = new String[validGroups.size()]; String[] labels = new String[validGroups.size()]; for (int i = 0; i < validGroups.size(); i++) { EvalGroup group = validGroups.get(i); values[i] = group.evalGroupId; labels[i] = group.title; } // show the switch group selection and form UIBranchContainer showSwitchGroup = UIBranchContainer.make(tofill, "show-switch-group:"); UIMessage.make(showSwitchGroup, "switch-group-header", "takeeval.switch.group.header"); UIForm chooseGroupForm = UIForm.make(showSwitchGroup, "switch-group-form", new EvalViewParameters(TakeEvalProducer.VIEW_ID, evaluationId, responseId, evalGroupId)); UISelect.make(chooseGroupForm, "switch-group-list", values, labels, "#{evalGroupId}"); } // fill in group title EvalGroup evalGroup = commonLogic.makeEvalGroupObject(evalGroupId); UIBranchContainer groupTitle = UIBranchContainer.make(tofill, "show-group-title:"); UIMessage.make(groupTitle, "group-title-header", "takeeval.group.title.header"); UIOutput.make(groupTitle, "group-title", evalGroup.title); if (LOG.isDebugEnabled()) { LOG.debug("Begin render of eval: " + eval.getTitle() + " (" + evaluationId + "), group: " + groupTitle + " (" + evalGroupId + ")"); } // show instructions if not null if (eval.getInstructions() != null && !("".equals(eval.getInstructions()))) { UIBranchContainer instructions = UIBranchContainer.make(tofill, "show-eval-instructions:"); UIMessage.make(instructions, "eval-instructions-header", "takeeval.instructions.header"); UIVerbatim.make(instructions, "eval-instructions", eval.getInstructions()); } // get the setting and make sure it cannot be null (fix for http://www.caret.cam.ac.uk/jira/browse/CTL-531) Boolean studentAllowedLeaveUnanswered = (Boolean) evalSettings .get(EvalSettings.STUDENT_ALLOWED_LEAVE_UNANSWERED); if (studentAllowedLeaveUnanswered == null) { studentAllowedLeaveUnanswered = EvalUtils.safeBool(eval.getBlankResponsesAllowed(), false); } // show a warning to the user if all items must be filled in if (studentAllowedLeaveUnanswered == false) { UIBranchContainer note = UIBranchContainer.make(tofill, "show-eval-note:"); UIMessage.make(note, "eval-note-text", "takeeval.user.must.answer.all.note"); } UIBranchContainer formBranch = UIBranchContainer.make(tofill, "form-branch:"); UIForm form = UIForm.make(formBranch, "evaluationForm"); // bind the evaluation and evalGroup to the ones in the take eval bean String evalOTP = "evaluationBeanLocator."; form.parameters .add(new UIELBinding("#{takeEvalBean.eval}", new ELReference(evalOTP + eval.getId()))); form.parameters.add(new UIELBinding("#{takeEvalBean.evalGroupId}", evalGroupId)); // BEGIN the complex task of rendering the evaluation items // make the TI data structure TemplateItemDataList tidl = new TemplateItemDataList(evaluationId, evalGroupId, evaluationService, authoringService, hierarchyLogic, null); Set<String> instructorIds = tidl.getAssociateIds(EvalConstants.ITEM_CATEGORY_INSTRUCTOR); Set<String> assistantIds = tidl.getAssociateIds(EvalConstants.ITEM_CATEGORY_ASSISTANT); List<String> associatedTypes = tidl.getAssociateTypes(); if (LOG.isDebugEnabled()) { LOG.debug("TIDL: eval=" + evaluationId + ", group=" + evalGroupId + ", items=" + tidl.getTemplateItemsCount() + " instructorIds: " + instructorIds + ", " + " associatedTypes: " + associatedTypes); } // SELECTION Code - EVALSYS-618 Boolean selectionsEnabled = (Boolean) evalSettings .get(EvalSettings.ENABLE_INSTRUCTOR_ASSISTANT_SELECTION); String instructorSelectionOption = EvalAssignGroup.SELECTION_OPTION_ALL; String assistantSelectionOption = EvalAssignGroup.SELECTION_OPTION_ALL; if (LOG.isDebugEnabled()) { LOG.debug("Selections: enabled=" + selectionsEnabled + ", inst=" + instructorSelectionOption + ", asst=" + assistantSelectionOption); } Map<String, String[]> savedSelections = new HashMap<>(); if (response != null) { savedSelections = response.getSelections(); } if (selectionsEnabled) { // only do the selection calculations if it is enabled EvalAssignGroup assignGroup = evaluationService.getAssignGroupByEvalAndGroupId(evaluationId, evalGroupId); Map<String, String> selectorType = new HashMap<>(); instructorSelectionOption = EvalUtils .getSelectionSetting(EvalAssignGroup.SELECTION_TYPE_INSTRUCTOR, assignGroup, null); selectorType.put(EvalConstants.ITEM_CATEGORY_INSTRUCTOR, instructorSelectionOption); Boolean assistantsEnabled = (Boolean) evalSettings.get(EvalSettings.ENABLE_ASSISTANT_CATEGORY); if (assistantsEnabled) { assistantSelectionOption = EvalUtils .getSelectionSetting(EvalAssignGroup.SELECTION_TYPE_ASSISTANT, assignGroup, null); selectorType.put(EvalConstants.ITEM_CATEGORY_ASSISTANT, assistantSelectionOption); } if (response != null) { // emit currently selected people into hidden element for JS use Set<String> savedIds = new HashSet<>(); for (String selectKey : savedSelections.keySet()) { String[] usersFound = savedSelections.get(selectKey); savedIds.add(usersFound[0]); } UIOutput savedSel = UIOutput.make(formBranch, "selectedPeopleInResponse", savedIds.toString()); savedSel.decorators = new DecoratorList( new UIIDStrategyDecorator("selectedPeopleInResponse")); } Iterator<Map.Entry<String, String>> selector = selectorType.entrySet().iterator(); while (selector.hasNext()) { Map.Entry<String, String> pairs = selector.next(); String selectKey = (String) pairs.getKey(); String selectKeyLowerCaps = selectKey.toLowerCase(); String selectValue = (String) pairs.getValue(); String uiTag = "select-" + selectKeyLowerCaps; String selectionOTP = "#{takeEvalBean.selection" + selectKeyLowerCaps + "Ids}"; Set<String> selectUserIds = new HashSet<>(); if (selectKeyLowerCaps.equals(SELECT_KEY_INSTRUCTOR)) { selectUserIds = instructorIds; } else if (selectKeyLowerCaps.equals(SELECT_KEY_ASSISTANT)) { selectUserIds = assistantIds; } // We render the selection controls if there are at least two // Instructors/TAs if (selectUserIds.size() > 1 && associatedTypes.contains(selectKey)) { if (selectValue.equals(EvalAssignGroup.SELECTION_OPTION_ALL)) { // nothing special to do in all case } else if (EvalAssignGroup.SELECTION_OPTION_MULTIPLE.equals(selectValue)) { UIBranchContainer showSwitchGroup = UIBranchContainer.make(form, uiTag + "-multiple:"); // Things for building the UISelect of Assignment Checkboxes List<String> assLabels = new ArrayList<>(); List<String> assValues = new ArrayList<>(); UISelect assSelect = UISelect.makeMultiple(showSwitchGroup, uiTag + "-multiple-holder", new String[] {}, new String[] {}, selectionOTP, new String[] {}); String assSelectID = assSelect.getFullID(); for (String userId : selectUserIds) { EvalUser user = commonLogic.getEvalUserById(userId); assValues.add(user.userId); assLabels.add(user.displayName); UIBranchContainer row = UIBranchContainer.make(showSwitchGroup, uiTag + "-multiple-row:"); UISelectChoice.make(row, uiTag + "-multiple-box", assSelectID, assLabels.size() - 1); UISelectLabel.make(row, uiTag + "-multiple-label", assSelectID, assLabels.size() - 1); } assSelect.optionlist = UIOutputMany.make(assValues.toArray(new String[] {})); assSelect.optionnames = UIOutputMany.make(assLabels.toArray(new String[] {})); } else if (EvalAssignGroup.SELECTION_OPTION_ONE.equals(selectValue)) { List<String> value = new ArrayList<>(); List<String> label = new ArrayList<>(); value.add("default"); label.add(messageLocator.getMessage("takeeval.selection.dropdown")); List<EvalUser> users = commonLogic.getEvalUsersByIds(new ArrayList(selectUserIds)); for (EvalUser user : users) { value.add(user.userId); label.add(user.displayName); } UIBranchContainer showSwitchGroup = UIBranchContainer.make(form, uiTag + "-one:"); UIOutput.make(showSwitchGroup, uiTag + "-one-header"); UISelect.make(showSwitchGroup, uiTag + "-one-list", value.toArray(new String[value.size()]), label.toArray(new String[label.size()]), selectionOTP); } else { throw new IllegalStateException("Invalid selection option (" + selectValue + "): do not know how to handle this."); } } else if (selectUserIds.size() == 1 && associatedTypes.contains(selectKey)) { // handle case where there are selections set but ONLY 1 user in the role. if (!selectValue.equals(EvalAssignGroup.SELECTION_OPTION_ALL)) { for (String userId : selectUserIds) { form.parameters.add(new UIELBinding(selectionOTP, userId)); } } } else { // handle case where there are selections set but no users in the roles. if (!selectValue.equals(EvalAssignGroup.SELECTION_OPTION_ALL)) { form.parameters.add(new UIELBinding(selectionOTP, "none")); } } } } // loop through the TIGs and handle each associated category Boolean useCourseCategoryOnly = (Boolean) evalSettings .get(EvalSettings.ITEM_USE_COURSE_CATEGORY_ONLY); if (LOG.isDebugEnabled()) { LOG.debug("TIGs: useCourseCategoryOnly=" + useCourseCategoryOnly); } for (TemplateItemGroup tig : tidl.getTemplateItemGroups()) { if (LOG.isDebugEnabled()) { LOG.debug("TIGs: tig.associateType=" + tig.associateType); } UIBranchContainer categorySectionBranch = UIBranchContainer.make(form, "categorySection:"); // only do headers if we are allowed to use categories if (!useCourseCategoryOnly) { // handle printing the category header if (EvalConstants.ITEM_CATEGORY_COURSE.equals(tig.associateType)) { UIMessage.make(categorySectionBranch, "categoryHeader", "takeeval.group.questions.header"); } else if (EvalConstants.ITEM_CATEGORY_INSTRUCTOR.equals(tig.associateType)) { showHeaders(categorySectionBranch, tig.associateType, tig.associateId, instructorIds, instructorSelectionOption, savedSelections); } else if (EvalConstants.ITEM_CATEGORY_ASSISTANT.equals(tig.associateType)) { showHeaders(categorySectionBranch, tig.associateType, tig.associateId, assistantIds, assistantSelectionOption, savedSelections); } } // loop through the hierarchy node groups if (LOG.isDebugEnabled()) { LOG.debug("TIGs: tig.hierarchyNodeGroups=" + tig.hierarchyNodeGroups.size()); } for (HierarchyNodeGroup hng : tig.hierarchyNodeGroups) { // render a node title if (hng.node != null) { // Showing the section title is system configurable via the administrate view Boolean showHierSectionTitle = (Boolean) evalSettings .get(EvalSettings.DISPLAY_HIERARCHY_HEADERS); if (showHierSectionTitle) { UIBranchContainer nodeTitleBranch = UIBranchContainer.make(categorySectionBranch, "itemrow:nodeSection"); UIOutput.make(nodeTitleBranch, "nodeTitle", hng.node.title); } } List<DataTemplateItem> dtis = hng.getDataTemplateItems(false); if (LOG.isDebugEnabled()) { LOG.debug("DTIs: count=" + dtis.size()); } for (int i = 0; i < dtis.size(); i++) { DataTemplateItem dti = dtis.get(i); UIBranchContainer nodeItemsBranch = UIBranchContainer.make(categorySectionBranch, "itemrow:templateItem"); if (i % 2 != 0) { nodeItemsBranch.decorate(new UIStyleDecorator("itemsListOddLine")); // must match the existing CSS class } renderItemPrep(nodeItemsBranch, form, dti, eval, missingKeys); } } } if (response != null && !response.complete) { UIMessage.make(tofill, "saveEvaluationWithoutSubmitWarning", "takeeval.saved.warning"); } Boolean saveEvaluationWithoutSubmit = (Boolean) evalSettings .get(EvalSettings.STUDENT_SAVE_WITHOUT_SUBMIT); if (saveEvaluationWithoutSubmit && (response == null || !response.complete)) { UICommand.make(form, "saveEvaluationWithoutSubmit", UIMessage.make("takeeval.save.button"), "#{takeEvalBean.saveEvaluationWithoutSubmit}"); } UICommand.make(form, "submitEvaluation", UIMessage.make("takeeval.submit.button"), "#{takeEvalBean.submitEvaluation}"); Boolean studentCancelAllowed = (Boolean) evalSettings.get(EvalSettings.STUDENT_CANCEL_ALLOWED); if (studentCancelAllowed) { UIMessage.make(form, "cancelEvaluation", "general.cancel.button"); // populate the URL we will activate (do NOT use summary-link here) UIInternalLink.make(tofill, "dashboard-link", new SimpleViewParameters(SummaryProducer.VIEW_ID)); } } else { // user cannot access eval so give them a sad message EvalUser current = commonLogic.getEvalUserById(currentUserId); UIMessage.make(tofill, "eval-cannot-take-message", "takeeval.user.cannot.take", new String[] { current.displayName, current.email, current.username }); LOG.info("User (" + currentUserId + ") cannot take evaluation: " + eval.getId()); } } } /** * Render each group header * @param categorySectionBranch the parent container * @param associateType Assistant or Instructor value * @param associateId userId for Assistant or Instructor * @param associateIds userIds for Assistants or Instructors * @param selectionOption Selection setting for this user * @param savedSelections */ private void showHeaders(UIBranchContainer categorySectionBranch, String associateType, String associateId, Set<String> associateIds, String selectionOption, Map<String, String[]> savedSelections) { EvalUser user = commonLogic.getEvalUserById(associateId); UIMessage header = UIMessage.make(categorySectionBranch, "categoryHeader", "takeeval." + associateType.toLowerCase() + ".questions.header", new Object[] { user.displayName }); // EVALSYS-618: support for JS: add display name to title attribute of // legend and hide category items header.decorators = new DecoratorList(new UIFreeAttributeDecorator("title", user.displayName)); categorySectionBranch.decorators = new DecoratorList( new UIFreeAttributeDecorator(new String[] { "name", "class" }, new String[] { user.userId, associateType.toLowerCase() + "Branch" })); if (!EvalAssignGroup.SELECTION_OPTION_ALL.equals(selectionOption) && associateIds.size() > 1) { Map<String, String> cssHide = new HashMap<>(); cssHide.put("display", "none"); categorySectionBranch.decorators.add(new UICSSDecorator(cssHide)); } } /** * Prepare to render an item, this handles blocks correctly * * @param parent the parent container * @param form the form this item will associate with * @param dti the wrapped template item we will render * @param eval the eval, needed for calculating rendering props * @param missingKeys the invalid keys, needed for calculating rendering props */ private void renderItemPrep(UIBranchContainer parent, UIForm form, DataTemplateItem dti, EvalEvaluation eval, Set<String> missingKeys) { if (LOG.isDebugEnabled()) { LOG.debug("renderItemPrep: eval=" + eval.getId() + ", dti=" + dti); } int displayIncrement = 0; // stores the increment in the display number String[] currentAnswerOTP = null; // holds array of bindings for items EvalTemplateItem templateItem = dti.templateItem; if (!TemplateItemUtils.isAnswerable(templateItem)) { // nothing to bind for unanswerable items unless it is a block parent if (dti.blockChildItems != null) { // Handle the BLOCK PARENT special case - block item being rendered // get the child items for this block List<EvalTemplateItem> childList = dti.blockChildItems; currentAnswerOTP = new String[childList.size()]; // for each child item, construct a binding for (int j = 0; j < childList.size(); j++) { EvalTemplateItem currChildItem = childList.get(j); // set up OTP paths String[] childAnswerOTP = setupCurrentAnswerBindings(form, currChildItem, dti.associateId); if (childAnswerOTP != null) { currentAnswerOTP[j] = childAnswerOTP[0]; } renderedItemCount++; } displayIncrement = currentAnswerOTP.length; } } else { // non-block and answerable items currentAnswerOTP = setupCurrentAnswerBindings(form, templateItem, dti.associateId); displayIncrement++; } // setup the render properties to send along Map<String, Object> renderProps = renderingUtils.makeRenderProps(dti, eval, missingKeys, null); // render the item if (LOG.isDebugEnabled()) { LOG.debug("render item: num=" + displayNumber + " (count=" + renderedItemCount + "), render=" + renderProps + ", templateItem=" + templateItem); } itemRenderer.renderItem(parent, "renderedItem:", currentAnswerOTP, templateItem, displayNumber, false, renderProps); /* increment the item counters, if we displayed 1 item, increment by 1, * if we displayed a block, renderedItem has been incremented for each child, increment displayNumber by the number of blockChildren, * here we are simply adding the display increment to the overall number -AZ */ displayNumber += displayIncrement; renderedItemCount++; } /** * Generates the correct OTP path for the current answer associated with this templateItem, * also handles the binding of the item to answer and the associatedId * @param form the form to bind this data into * @param templateItem the template item which the answer should associate with * @param associatedId the associated ID to bind this TI with * @return an array of binding strings from the TI to the answer (first) and NA (second) which will bind to the input elements */ private String[] setupCurrentAnswerBindings(UIForm form, EvalTemplateItem templateItem, String associatedId) { // the associated type is set only if the associatedId is set String associatedType = null; if (associatedId != null) { associatedType = templateItem.getCategory(); // type will match the template item category } // set up OTP paths for answerable items String responseAnswersOTP = "responseAnswersBeanLocator."; String currAnswerOTP; boolean newAnswer; if (responseId == null) { // it should not be the case that we have no response //throw new IllegalStateException("There is no response, something has failed to load correctly for takeeval"); // EVALSYS-360 - have to use this again and add a binding for start time form.parameters.add(new UIELBinding("takeEvalBean.startDate", new Date())); currAnswerOTP = responseAnswersOTP + ResponseAnswersBeanLocator.NEW_1 + "." + ResponseAnswersBeanLocator.NEW_PREFIX + renderedItemCount + "."; newAnswer = true; } else { // if the user has answered this question before, point at their response String key = TemplateItemUtils.makeTemplateItemAnswerKey(templateItem.getId(), associatedType, associatedId); EvalAnswer currAnswer = answerMap.get(key); if (currAnswer == null) { // this is a new answer newAnswer = true; currAnswerOTP = responseAnswersOTP + responseId + "." + ResponseAnswersBeanLocator.NEW_PREFIX + (renderedItemCount) + "."; } else { // existing answer newAnswer = false; currAnswerOTP = responseAnswersOTP + responseId + "." + currAnswer.getId() + "."; } } if (newAnswer) { // ADD in the bindings for the new answers // bind the template item to the answer form.parameters.add(new UIELBinding(currAnswerOTP + "templateItem", new ELReference("templateItemWBL." + templateItem.getId()))); // bind the item to the answer form.parameters.add(new UIELBinding(currAnswerOTP + "item", new ELReference("itemWBL." + templateItem.getItem().getId()))); // bind the associated id (current instructor id or environment) and type to the current answer if (associatedId != null) { // only do the binding if this is not null, otherwise it will bind in empty strings form.parameters.add(new UIELBinding(currAnswerOTP + "associatedId", associatedId)); form.parameters.add(new UIELBinding(currAnswerOTP + "associatedType", associatedType)); } } // generate binding for the UI input element (UIInput, UISelect, etc.) to the correct part of answer String[] bindings = new String[3]; // set the primary binding depending on the item type String itemType = TemplateItemUtils.getTemplateItemType(templateItem); if (EvalConstants.ITEM_TYPE_MULTIPLEANSWER.equals(itemType)) { bindings[0] = currAnswerOTP + "multipleAnswers"; } else if (EvalConstants.ITEM_TYPE_TEXT.equals(itemType)) { bindings[0] = currAnswerOTP + "text"; } else { // this is the default binding (scaled and MC) bindings[0] = currAnswerOTP + "numeric"; } // set the NA and comment bindings bindings[1] = currAnswerOTP + "NA"; bindings[2] = currAnswerOTP + "comment"; return bindings; } /* (non-Javadoc) * @see uk.org.ponder.rsf.viewstate.ViewParamsReporter#getViewParameters() */ public ViewParameters getViewParameters() { return new EvalViewParameters(); } /* (non-Javadoc) * @see uk.org.ponder.rsf.flow.jsfnav.NavigationCaseReporter#reportNavigationCases() */ public List<NavigationCase> reportNavigationCases() { List<NavigationCase> i = new ArrayList<>(); i.add(new NavigationCase("success", new SimpleViewParameters(SummaryProducer.VIEW_ID))); return i; } /* (non-Javadoc) * @see uk.org.ponder.rsf.flow.ActionResultInterceptor#interceptActionResult(uk.org.ponder.rsf.flow.ARIResult, uk.org.ponder.rsf.viewstate.ViewParameters, java.lang.Object) */ public void interceptActionResult(ARIResult result, ViewParameters incoming, Object actionReturn) { EvalViewParameters etvp = (EvalViewParameters) incoming; if (etvp.evalCategory != null) { result.resultingView = new EvalCategoryViewParameters(ShowEvalCategoryProducer.VIEW_ID, etvp.evalCategory); } } }