org.sakaiproject.evaluation.tool.producers.TakeEvalProducer.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.evaluation.tool.producers.TakeEvalProducer.java

Source

/**
 * 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);
        }
    }

}