Package org.olat.course.assessment

Source Code of org.olat.course.assessment.AssessmentMainController

/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/

package org.olat.course.assessment;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringEscapeUtils;
import org.olat.admin.securitygroup.gui.UserControllerFactory;
import org.olat.admin.user.UserTableDataModel;
import org.olat.basesecurity.Manager;
import org.olat.basesecurity.ManagerFactory;
import org.olat.basesecurity.SecurityGroup;
import org.olat.core.commons.fullWebApp.LayoutMain3ColsController;
import org.olat.core.commons.persistence.DBFactory;
import org.olat.core.commons.persistence.PersistenceHelper;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.link.Link;
import org.olat.core.gui.components.link.LinkFactory;
import org.olat.core.gui.components.panel.Panel;
import org.olat.core.gui.components.table.ColumnDescriptor;
import org.olat.core.gui.components.table.CustomRenderColumnDescriptor;
import org.olat.core.gui.components.table.DefaultColumnDescriptor;
import org.olat.core.gui.components.table.Table;
import org.olat.core.gui.components.table.TableController;
import org.olat.core.gui.components.table.TableEvent;
import org.olat.core.gui.components.table.TableGuiConfiguration;
import org.olat.core.gui.components.tree.GenericTreeModel;
import org.olat.core.gui.components.tree.GenericTreeNode;
import org.olat.core.gui.components.tree.MenuTree;
import org.olat.core.gui.components.tree.TreeModel;
import org.olat.core.gui.components.tree.TreeNode;
import org.olat.core.gui.components.velocity.VelocityContainer;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.controller.MainLayoutBasicController;
import org.olat.core.gui.control.generic.dtabs.Activateable;
import org.olat.core.gui.control.generic.messages.MessageUIFactory;
import org.olat.core.gui.control.generic.tool.ToolController;
import org.olat.core.gui.control.generic.tool.ToolFactory;
import org.olat.core.gui.translator.PackageTranslator;
import org.olat.core.gui.translator.Translator;
import org.olat.core.id.Identity;
import org.olat.core.id.IdentityEnvironment;
import org.olat.core.id.OLATResourceable;
import org.olat.core.id.Roles;
import org.olat.core.id.context.BusinessControl;
import org.olat.core.id.context.ContextEntry;
import org.olat.core.logging.OLATSecurityException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.logging.activity.ActionType;
import org.olat.core.util.Util;
import org.olat.core.util.event.GenericEventListener;
import org.olat.core.util.notifications.ContextualSubscriptionController;
import org.olat.core.util.notifications.PublisherData;
import org.olat.core.util.notifications.SubscriptionContext;
import org.olat.core.util.resource.OresHelper;
import org.olat.course.CourseFactory;
import org.olat.course.ICourse;
import org.olat.course.condition.Condition;
import org.olat.course.condition.interpreter.ConditionExpression;
import org.olat.course.condition.interpreter.OnlyGroupConditionInterpreter;
import org.olat.course.groupsandrights.CourseGroupManager;
import org.olat.course.nodes.AssessableCourseNode;
import org.olat.course.nodes.CourseNode;
import org.olat.course.nodes.CourseNodeFactory;
import org.olat.course.nodes.STCourseNode;
import org.olat.course.run.userview.UserCourseEnvironment;
import org.olat.course.run.userview.UserCourseEnvironmentImpl;
import org.olat.group.BusinessGroup;
import org.olat.group.ui.context.BGContextTableModel;
import org.olat.user.UserManager;

/**
* Initial Date:  Jun 18, 2004
*
* @author gnaegi
*
* Comment:
* This contoller can be used to control and change user score, passed, attempts and comment variables.
* It provides a menu that allows three different access paths to the same data: user centric, group
* centric or course node centric.
*/
public class AssessmentMainController extends MainLayoutBasicController implements Activateable, GenericEventListener {
  OLog log = Tracing.createLoggerFor(AssessmentMainController.class);

  private static final String CMD_INDEX       = "cmd.index";
  private static final String CMD_USERFOCUS   = "cmd.userfocus";
  private static final String CMD_GROUPFOCUS   = "cmd.groupfocus";
  private static final String CMD_NODEFOCUS   = "cmd.nodefocus";
  private static final String CMD_BULKFOCUS   = "cmd.bulkfocus";

  private static final String CMD_CHOOSE_GROUP = "cmd.choose.group";
  private static final String CMD_CHOOSE_USER = "cmd.choose.user";
  private static final String CMD_SELECT_NODE = "cmd.select.node";
 
  private static final int MODE_USERFOCUS    = 0;
  private static final int MODE_GROUPFOCUS  = 1;
  private static final int MODE_NODEFOCUS    = 2;
  private static final int MODE_BULKFOCUS    = 3;
  private int mode;

  private IAssessmentCallback callback;
  private MenuTree menuTree;
  private Panel main;
 
  private ToolController toolC;
  private VelocityContainer index, groupChoose, userChoose, nodeChoose, wrapper;

  private NodeTableDataModel nodeTableModel;
  private TableController groupListCtr, userListCtr, nodeListCtr;
  private List nodeFilters;
  private List<Identity> identitiesList;

  // Course assessment notification support fields 
  private ContextualSubscriptionController csc;
 
  // Hash map to keep references to already created user course environments
  // Serves as a local cache to reduce database access - not shared by multiple threads
  Map<Long, UserCourseEnvironment> localUserCourseEnvironmentCache; // package visibility for avoiding synthetic accessor method
  // List of groups to which the user has access rights in this course
  private List<BusinessGroup> coachedGroups;

  // some state variables
  private AssessableCourseNode currentCourseNode;
  private AssessedIdentityWrapper assessedIdentityWrapper;
  private AssessmentEditController assessmentEditController;
  private IdentityAssessmentEditController identityAssessmentController;
  private BusinessGroup currentGroup;
 
  private Thread assessmentCachPreloaderThread;
  private Link backLinkUC;
  private Link backLinkGC;
  private Link allUsersButton;
 
  private boolean isAdministrativeUser;
  private Translator propertyHandlerTranslator;
 
  private boolean isFiltering = true;
  private Link showAllCourseNodesButton;
  private Link filterCourseNodesButton;
 
  private BulkAssessmentMainController bamc;

  private OLATResourceable ores;
 
  /**
   * Constructor for the assessment tool controller.
   * @param ureq
   * @param wControl
   * @param course
   * @param assessmentCallback
   */
  public AssessmentMainController(UserRequest ureq, WindowControl wControl, OLATResourceable ores, IAssessmentCallback assessmentCallback) {
    super(ureq, wControl);   
   
    getUserActivityLogger().setStickyActionType(ActionType.admin);
    this.ores = ores;
    this.callback = assessmentCallback;
    this.localUserCourseEnvironmentCache = new HashMap<Long, UserCourseEnvironment>();
   
    //use the PropertyHandlerTranslator  as tableCtr translator
    propertyHandlerTranslator = UserManager.getInstance().getPropertyHandlerTranslator(getTranslator());
   
    Roles roles = ureq.getUserSession().getRoles();
    isAdministrativeUser = (roles.isAuthor() || roles.isGroupManager() || roles.isUserManager() || roles.isOLATAdmin());   
   
    main = new Panel("assessmentmain");

    // Intro page, static
    index = createVelocityContainer("assessment_index");
   
    Identity focusOnIdentity = null;
    ICourse course = CourseFactory.loadCourse(ores);
    boolean hasAssessableNodes = course.hasAssessableNodes();
    if (hasAssessableNodes) {
      BusinessControl bc = getWindowControl().getBusinessControl();
      ContextEntry ceIdentity = bc.popLauncherContextEntry();
      if (ceIdentity != null) {
        OLATResourceable oresIdentity = ceIdentity.getOLATResourceable();
        if (OresHelper.isOfType(oresIdentity, Identity.class)) {
          Long identityKey = oresIdentity.getResourceableId();
          focusOnIdentity = ManagerFactory.getManager().loadIdentityByKey(identityKey);
        }
      }

      index.contextPut("hasAssessableNodes", Boolean.TRUE);
     
      // --- assessment notification subscription ---
      AssessmentNotificationsHandler anh = AssessmentNotificationsHandler.getInstance();
      SubscriptionContext subsContext = anh.getAssessmentSubscriptionContext(ureq.getIdentity(), course);
      if (subsContext != null) {
        PublisherData pData = anh.getAssessmentPublisherData(course, wControl.getBusinessControl().getAsString());
        csc = new ContextualSubscriptionController(ureq, getWindowControl(), subsContext, pData);
        listenTo(csc); // cleanup on dispose
        index.put("assessmentSubscription", csc.getInitialComponent());
      }
     
      // Wrapper container: adds header
      wrapper = createVelocityContainer("wrapper");
     
      // Init the group and the user chooser view velocity container
      groupChoose = createVelocityContainer("groupchoose");
      allUsersButton = LinkFactory.createButtonSmall("cmd.all.users", groupChoose, this);
      groupChoose.contextPut("isFiltering", Boolean.TRUE);
      backLinkGC = LinkFactory.createLinkBack(groupChoose, this);
 
      userChoose = createVelocityContainer("userchoose");
      showAllCourseNodesButton = LinkFactory.createButtonSmall("cmd.showAllCourseNodes", userChoose, this);
      filterCourseNodesButton  = LinkFactory.createButtonSmall("cmd.filterCourseNodes", userChoose, this);
      userChoose.contextPut("isFiltering", Boolean.TRUE);
      backLinkUC = LinkFactory.createLinkBack(userChoose, this);
     
      nodeChoose = createVelocityContainer("nodechoose");

      // Initialize all groups that the user is allowed to coach
      coachedGroups = getAllowedGroupsFromGroupmanagement(ureq.getIdentity());

      // preload the assessment cache to speed up everything as background thread
      // the thread will terminate when finished
      assessmentCachPreloaderThread = new AssessmentCachePreloadThread("assessmentCachPreloader-" + course.getResourceableId());
      assessmentCachPreloaderThread.setDaemon(true);
      assessmentCachPreloaderThread.start();
     
    } else {
      index.contextPut("hasAssessableNodes", Boolean.FALSE);     
    }
     
    // Navigation menu
    menuTree = new MenuTree("menuTree");
    TreeModel tm = buildTreeModel(hasAssessableNodes);
    menuTree.setTreeModel(tm);
    menuTree.setSelectedNodeId(tm.getRootNode().getIdent());
    menuTree.addListener(this);

    // Tool and action box
    toolC = ToolFactory.createToolController(getWindowControl());
    listenTo(toolC);
    toolC.addHeader(translate("tool.name"));
    toolC.addLink("cmd.close", translate("command.closeassessment"), null, "b_toolbox_close");

    // Start on index page
    main.setContent(index);
    LayoutMain3ColsController columLayoutCtr = new LayoutMain3ColsController(ureq, getWindowControl(), menuTree, toolC.getInitialComponent(), main, "course" + course.getResourceableId());
    listenTo(columLayoutCtr); // cleanup on dispose
    putInitialPanel(columLayoutCtr.getInitialComponent());
   
    if(focusOnIdentity != null) {
      //fill the user list for the
      this.mode = MODE_USERFOCUS;
      this.identitiesList = getAllIdentitisFromGroupmanagement();
      doSimpleUserChoose(ureq, this.identitiesList);
     
      GenericTreeModel menuTreeModel = (GenericTreeModel)menuTree.getTreeModel();
      TreeNode userNode = menuTreeModel.findNodeByUserObject(CMD_USERFOCUS);
      if(userNode != null) {
        menuTree.setSelectedNode(userNode);
      }
     
      // select user
      this.assessedIdentityWrapper = AssessmentHelper.wrapIdentity(focusOnIdentity, this.localUserCourseEnvironmentCache, course, null);
     
      UserCourseEnvironment chooseUserCourseEnv = assessedIdentityWrapper.getUserCourseEnvironment();   
      identityAssessmentController = new IdentityAssessmentEditController(getWindowControl(),ureq, chooseUserCourseEnv, course, true);
      listenTo(identityAssessmentController);
      setContent(identityAssessmentController.getInitialComponent());
    }
   
    // Register for assessment changed events
    course.getCourseEnvironment().getAssessmentManager().registerForAssessmentChangeEvents(this, ureq.getIdentity());
  }

  /**
   * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest,
   *      org.olat.core.gui.components.Component, org.olat.core.gui.control.Event)
   */
  public void event(UserRequest ureq, Component source, Event event) {
    if (source == menuTree) {
      disposeChildControllerAndReleaseLocks(); // first cleanup old locks
      if (event.getCommand().equals(MenuTree.COMMAND_TREENODE_CLICKED)) {
        TreeNode selTreeNode = menuTree.getSelectedNode();
        String cmd = (String) selTreeNode.getUserObject();
        // reset helper variables
        this.currentCourseNode = null;
        this.currentGroup = null;
        this.isFiltering = true;
        if (cmd.equals(CMD_INDEX)) {
          main.setContent(index);
        } else if (cmd.equals(CMD_USERFOCUS)) {
          this.mode = MODE_USERFOCUS;
          this.identitiesList = getAllIdentitisFromGroupmanagement();
          doSimpleUserChoose(ureq, this.identitiesList);
        } else if (cmd.equals(CMD_GROUPFOCUS)) {
          this.mode = MODE_GROUPFOCUS;
          doGroupChoose(ureq);
        } else if (cmd.equals(CMD_NODEFOCUS)) {
          this.mode = MODE_NODEFOCUS;
          doNodeChoose(ureq);
        } else if (cmd.equals(CMD_BULKFOCUS)){
          this.mode = MODE_BULKFOCUS;
          doBulkChoose(ureq);
        }
      }
    } else if (source == allUsersButton){ 
      this.identitiesList = getAllIdentitisFromGroupmanagement();
      // Init the user list with this identitites list
      this.currentGroup = null;
      doUserChooseWithData(ureq, this.identitiesList, null, this.currentCourseNode);
    } else if (source == backLinkGC){
      setContent(nodeListCtr.getInitialComponent());
    } else if (source == backLinkUC){
      setContent(groupChoose);
    } else if (source == showAllCourseNodesButton) {
      enableFilteringCourseNodes(false);
    else if (source == filterCourseNodesButton) {
      enableFilteringCourseNodes(true);
    }
  }

  /**
   * Enable/disable filtering of course-nodes in user-selection table
   * and update new course-node-list.
   * (Assessemnt-tool => 
   * @param enableFiltering
   */
  private void enableFilteringCourseNodes(boolean enableFiltering) {
    ICourse course = CourseFactory.loadCourse(ores);
    this.isFiltering = enableFiltering;
    userChoose.contextPut("isFiltering", enableFiltering);
    this.nodeFilters = addAssessableNodesToList(course.getRunStructure().getRootNode(), this.currentGroup);
    userListCtr.setFilters(this.nodeFilters, null );
  }

  /**
   * Enable/disable filtering of groups in
   * @param enableFiltering
   * @param ureq
   */
  private void enableFilteringGroups(boolean enableFiltering, UserRequest ureq) {
    this.isFiltering = enableFiltering;
    groupChoose.contextPut("isFiltering", enableFiltering);
    doGroupChoose(ureq);
  }

  /**
   * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest,
   *      org.olat.core.gui.control.Controller, org.olat.core.gui.control.Event)
   */
  public void event(UserRequest ureq, Controller source, Event event) {
    if (source == toolC) {
      if (event.getCommand().equals("cmd.close")) {
        disposeChildControllerAndReleaseLocks(); // cleanup locks from children
        fireEvent(ureq, Event.DONE_EVENT);
      }
    }
    else if (source == groupListCtr) {
      if (event.getCommand().equals(Table.COMMANDLINK_ROWACTION_CLICKED)) {
        TableEvent te = (TableEvent) event;
        String actionid = te.getActionId();
        if (actionid.equals(CMD_CHOOSE_GROUP)) {
          int rowid = te.getRowId();
          GroupAndContextTableModel groupListModel = (GroupAndContextTableModel) groupListCtr.getTableDataModel();
          this.currentGroup = groupListModel.getBusinessGroupAt(rowid);
          this.identitiesList = getGroupIdentitiesFromGroupmanagement(this.currentGroup);
          // Init the user list with this identitites list
          doUserChooseWithData(ureq, this.identitiesList, this.currentGroup, this.currentCourseNode);
        }
      }
    }
    else if (source == userListCtr) {
      if (event.getCommand().equals(Table.COMMANDLINK_ROWACTION_CLICKED)) {
        TableEvent te = (TableEvent) event;
        String actionid = te.getActionId();
        if (actionid.equals(CMD_CHOOSE_USER)) {
          int rowid = te.getRowId();
          ICourse course = CourseFactory.loadCourse(ores);
          if (userListCtr.getTableDataModel() instanceof UserTableDataModel) {
            // in user MODE_USERFOCUS, a simple identity table is used, no wrapped identites
            UserTableDataModel userListModel = (UserTableDataModel) userListCtr.getTableDataModel();
            Identity assessedIdentity = userListModel.getIdentityAt(rowid);
            this.assessedIdentityWrapper = AssessmentHelper.wrapIdentity(assessedIdentity,
                this.localUserCourseEnvironmentCache, course, null);
          } else {
            // all other cases where user can be choosen the assessed identity wrapper is used
            AssessedIdentitiesTableDataModel userListModel = (AssessedIdentitiesTableDataModel) userListCtr.getTableDataModel();
            this.assessedIdentityWrapper = userListModel.getWrappedIdentity(rowid);
          }
          // init edit controller for this identity and this course node
          // or use identity assessment overview if no course node is defined
          if (this.currentCourseNode == null) {
            UserCourseEnvironment chooseUserCourseEnv = assessedIdentityWrapper.getUserCourseEnvironment();   
            removeAsListenerAndDispose(identityAssessmentController);
            identityAssessmentController = new IdentityAssessmentEditController(getWindowControl(),ureq, chooseUserCourseEnv, course, true);
            listenTo(identityAssessmentController);
            setContent(identityAssessmentController.getInitialComponent());
          } else {
            removeAsListenerAndDispose(assessmentEditController);
            assessmentEditController = new AssessmentEditController(ureq, getWindowControl(),course, currentCourseNode, assessedIdentityWrapper);
            listenTo(assessmentEditController);
            main.setContent(assessmentEditController.getInitialComponent());
          }
        }
      } else if (event.equals(TableController.EVENT_FILTER_SELECTED)) {
        this.currentCourseNode = (AssessableCourseNode) userListCtr.getActiveFilter();
        doUserChooseWithData(ureq, this.identitiesList, this.currentGroup, this.currentCourseNode);
      }
    }
    else if (source == nodeListCtr) {
      if (event.getCommand().equals(Table.COMMANDLINK_ROWACTION_CLICKED)) {
        TableEvent te = (TableEvent) event;
        String actionid = te.getActionId();
        if (actionid.equals(CMD_SELECT_NODE)) {
          ICourse course = CourseFactory.loadCourse(ores);
          int rowid = te.getRowId();
          Map<String,Object> nodeData = (Map<String,Object>) nodeTableModel.getObject(rowid);
          CourseNode node = course.getRunStructure().getNode((String) nodeData.get(AssessmentHelper.KEY_IDENTIFYER));
          this.currentCourseNode = (AssessableCourseNode) node;
          // cast should be save, only assessable nodes are selectable
          doGroupChoose(ureq);
        }
      } else if (event.equals(TableController.EVENT_FILTER_SELECTED)) {
        this.currentCourseNode = (AssessableCourseNode) nodeListCtr.getActiveFilter();
        doUserChooseWithData(ureq, this.identitiesList, null, this.currentCourseNode);
      }
    }
    else if (source == assessmentEditController) {
      if (event.equals(Event.CHANGED_EVENT)) {
        // refresh identity in list model
        if (userListCtr != null
            && userListCtr.getTableDataModel() instanceof AssessedIdentitiesTableDataModel) {
          AssessedIdentitiesTableDataModel atdm = (AssessedIdentitiesTableDataModel) userListCtr.getTableDataModel();
          List<AssessedIdentityWrapper> aiwList = atdm.getObjects();
          if (aiwList.contains(this.assessedIdentityWrapper)) {
            ICourse course = CourseFactory.loadCourse(ores);
            aiwList.remove(this.assessedIdentityWrapper);
            this.assessedIdentityWrapper = AssessmentHelper.wrapIdentity(this.assessedIdentityWrapper.getIdentity(),
            this.localUserCourseEnvironmentCache, course, currentCourseNode);
            aiwList.add(this.assessedIdentityWrapper);
            userListCtr.modelChanged();
          }
        }
      } // else nothing special to do
      setContent(userChoose);
    }
    else if (source == identityAssessmentController) {
      if (event.equals(Event.CANCELLED_EVENT)) {
        setContent(userChoose);
      }
    }
  }

  /**
   * @see org.olat.core.util.event.GenericEventListener#event(org.olat.core.gui.control.Event)
   */
  public void event(Event event) {
    if ((event instanceof AssessmentChangedEvent) &&  event.getCommand().equals(AssessmentChangedEvent.TYPE_SCORE_EVAL_CHANGED)) {
      AssessmentChangedEvent ace = (AssessmentChangedEvent) event;
      doUpdateLocalCacheAndUserModelFromAssessmentEvent(ace);
    }
  }
 
  /**
   * Notify subscribers when test are passed or attemps count change
   * EXPERIMENTAL!!!!! 
   */
  /*private void doNotifyAssessmentEvent(AssessmentChangedEvent ace) {
    String assessmentChangeType = ace.getCommand();
    // notify only comment has been changed
    if (assessmentChangeType == AssessmentChangedEvent.TYPE_PASSED_CHANGED
      || assessmentChangeType == AssessmentChangedEvent.TYPE_ATTEMPTS_CHANGED)
    {
      // if notification is enabled -> notify the publisher about news
      if (subsContext != null)
      {
        NotificationsManagerImpl.getInstance().markPublisherNews(subsContext, ace.getIdentity());
      }
    }
  }*/
   
 
  /**
   * Updates the local user course environment cache if the given event is for an identity
   * cached in the local cache. Also updates the user list table model if the identity from
   * the event is in the model.
   * @param ace
   */
  private void doUpdateLocalCacheAndUserModelFromAssessmentEvent(AssessmentChangedEvent ace) {
    String assessmentChangeType = ace.getCommand();
    // do not re-evaluate things if only comment has been changed
    if (assessmentChangeType.equals(AssessmentChangedEvent.TYPE_SCORE_EVAL_CHANGED)
        || assessmentChangeType.equals(AssessmentChangedEvent.TYPE_ATTEMPTS_CHANGED)) {

      // Check if the identity in the event is in our local user course environment
      // cache. If so, update the look-uped users score accounting information.
      //Identity identityFromEvent = ace.getIdentity();
      Long identityKeyFromEvent = ace.getIdentityKey();
      if (localUserCourseEnvironmentCache.containsKey(identityKeyFromEvent)) {
        UserCourseEnvironment uce = localUserCourseEnvironmentCache.get(identityKeyFromEvent);         
        // 1) update score accounting
        if (uce != null) {
          uce.getScoreAccounting().evaluateAll();
        }
        // 2) update user table model
        if (userListCtr != null
            && userListCtr.getTableDataModel() instanceof AssessedIdentitiesTableDataModel) {
          // 2.1) search wrapper object in model
          AssessedIdentitiesTableDataModel aitd = (AssessedIdentitiesTableDataModel) userListCtr.getTableDataModel();
          List<AssessedIdentityWrapper> wrappers = aitd.getObjects();
          Iterator<AssessedIdentityWrapper> iter = wrappers.iterator();
          AssessedIdentityWrapper wrappedIdFromModel = null;
          while (iter.hasNext()) {
            AssessedIdentityWrapper wrappedId =  iter.next();
            if (wrappedId.getIdentity().getKey().equals(identityKeyFromEvent)) {
              wrappedIdFromModel = wrappedId;
            }
          }
          // 2.2) update wrapper object
          if (wrappedIdFromModel != null) {
            wrappers.remove(wrappedIdFromModel);
            wrappedIdFromModel = AssessmentHelper.wrapIdentity(wrappedIdFromModel.getUserCourseEnvironment(), currentCourseNode);
            wrappers.add(wrappedIdFromModel);
            userListCtr.modelChanged();               
          }
        }
      }
      // else user not in our local cache -> nothing to do
    }
  }

  /**
   * @param selectedGroup
   * @return List of participant identities from this group
   */
  private List<Identity> getGroupIdentitiesFromGroupmanagement(BusinessGroup selectedGroup) {
    SecurityGroup selectedSecurityGroup = selectedGroup.getPartipiciantGroup();
    return ManagerFactory.getManager().getIdentitiesOfSecurityGroup(selectedSecurityGroup);
  }

  /**
   * @return List of all course participants
   */
  List<Identity> getAllIdentitisFromGroupmanagement() {
    List<Identity> allUsersList = new ArrayList<Identity>();
    Manager secMgr = ManagerFactory.getManager();
    Iterator<BusinessGroup> iter = this.coachedGroups.iterator();
    while (iter.hasNext()) {
      BusinessGroup group = iter.next();
      SecurityGroup secGroup = group.getPartipiciantGroup();
      List<Identity> identities = secMgr.getIdentitiesOfSecurityGroup(secGroup);
      for (Iterator<Identity> identitiyIter = identities.iterator(); identitiyIter.hasNext();) {
        Identity identity = identitiyIter.next();
        if (!PersistenceHelper.listContainsObjectByKey(allUsersList, identity)) {
          // only add if not already in list
          allUsersList.add(identity);
        }
      }
    }
    return allUsersList;
  }

  /**
   * @param identity
   * @return List of all course groups if identity is course admin, else groups that
   * are coached by this identity
   */
  private List<BusinessGroup> getAllowedGroupsFromGroupmanagement(Identity identity) {
    ICourse course = CourseFactory.loadCourse(ores);
    CourseGroupManager gm = course.getCourseEnvironment().getCourseGroupManager();
    if (callback.mayAssessAllUsers() || callback.mayViewAllUsersAssessments()) {
      return gm.getAllLearningGroupsFromAllContexts();
    } else if (callback.mayAssessCoachedUsers()) {
      return  gm.getOwnedLearningGroupsFromAllContexts(identity);
    } else {
      throw new OLATSecurityException("No rights to assess or even view any groups");
    }
  }
 

  /**
   * Initialize the group list table according to the users access rights
   * @param ureq The user request
   */
  private void doGroupChoose(UserRequest ureq) {
    ICourse course = CourseFactory.loadCourse(ores);
    removeAsListenerAndDispose(groupListCtr);
    TableGuiConfiguration tableConfig = new TableGuiConfiguration();
    tableConfig.setTableEmptyMessage(translate("groupchoose.nogroups"));
    groupListCtr = new TableController(tableConfig, ureq, getWindowControl(), getTranslator(), null);
    listenTo(groupListCtr);
    groupListCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.group.name", 0, CMD_CHOOSE_GROUP, ureq.getLocale()));
    groupListCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.group.desc", 1, null, ureq.getLocale()));
    CourseGroupManager gm = course.getCourseEnvironment().getCourseGroupManager();
    if (gm.getLearningGroupContexts().size() > 1) {
    // show groupcontext row only if multiple contexts are found
      groupListCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.group.context", 2, null, ureq.getLocale()));
    }
   
    Translator defaultContextTranslator = new PackageTranslator(Util.getPackageName(BGContextTableModel.class), ureq.getLocale());
    // loop over all groups to filter depending on condition
    List<BusinessGroup> currentGroups = new ArrayList<BusinessGroup>();
    for (Iterator iter = this.coachedGroups.iterator(); iter.hasNext();) {
      BusinessGroup group = (BusinessGroup) iter.next();
      if ( !isFiltering || isVisibleAndAccessable(this.currentCourseNode, group) ) {
        currentGroups.add(group);
      }
    }
    GroupAndContextTableModel groupTableDataModel = new GroupAndContextTableModel(currentGroups, defaultContextTranslator);
    groupListCtr.setTableDataModel(groupTableDataModel);
    groupChoose.put("grouplisttable", groupListCtr.getInitialComponent());
   
    // render all-groups button only if goups are available
    if (this.coachedGroups.size() > 0) groupChoose.contextPut("hasGroups", Boolean.TRUE);
    else groupChoose.contextPut("hasGroups", Boolean.FALSE);

    if (mode == MODE_NODEFOCUS) {
      groupChoose.contextPut("showBack", Boolean.TRUE);
    } else {
      groupChoose.contextPut("showBack", Boolean.FALSE);
    }
   
    // set main content to groupchoose
    setContent(groupChoose);
  }

  private void doUserChooseWithData(UserRequest ureq, List<Identity> identities, BusinessGroup group, AssessableCourseNode courseNode) {
    ICourse course = CourseFactory.loadCourse(ores);
    if (mode == MODE_GROUPFOCUS) {
      this.nodeFilters = addAssessableNodesToList(course.getRunStructure().getRootNode(), group);
      if (courseNode == null && this.nodeFilters.size() > 0) {
        this.currentCourseNode = (AssessableCourseNode) this.nodeFilters.get(0);
        courseNode = this.currentCourseNode;
      }
    }   
    // Init table headers
    removeAsListenerAndDispose(userListCtr);
    TableGuiConfiguration tableConfig = new TableGuiConfiguration();
    tableConfig.setTableEmptyMessage(translate("userchoose.nousers"));
   
    if (mode == MODE_GROUPFOCUS) {
      userListCtr = new TableController(tableConfig, ureq, getWindowControl(),
          this.nodeFilters, courseNode,
          translate("nodesoverview.filter.title"), null,propertyHandlerTranslator, null);
    } else {
      userListCtr = new TableController(tableConfig, ureq, getWindowControl(), propertyHandlerTranslator, null);
    }
    listenTo(userListCtr);
       
    // Wrap identities with user course environment and user score view
    List<AssessedIdentityWrapper> wrappedIdentities = new ArrayList<AssessedIdentityWrapper>();
    for (int i = 0; i < identities.size(); i++) {
      Identity identity = (Identity) identities.get(i);
      // if course node is null the wrapper will only contain the identity and no score information
      AssessedIdentityWrapper aiw = AssessmentHelper.wrapIdentity(identity,
      this.localUserCourseEnvironmentCache, course, courseNode);
      wrappedIdentities.add(aiw);
    }
    // Add the wrapped identities to the table data model
    AssessedIdentitiesTableDataModel tdm = new AssessedIdentitiesTableDataModel(wrappedIdentities, courseNode, ureq.getLocale(), isAdministrativeUser);
    tdm.addColumnDescriptors(userListCtr, CMD_CHOOSE_USER, mode == MODE_NODEFOCUS || mode == MODE_GROUPFOCUS);
    userListCtr.setTableDataModel(tdm);


    if (mode == MODE_USERFOCUS) {
      userChoose.contextPut("showBack", Boolean.FALSE);
    } else {
      userChoose.contextPut("showBack", Boolean.TRUE);
      if (mode == MODE_NODEFOCUS) {
        userChoose.contextPut("showFilterButton", Boolean.FALSE);
      } else {
        userChoose.contextPut("showFilterButton", Boolean.TRUE);
      }
    }
   
   
    if (group == null) {
      userChoose.contextPut("showGroup", Boolean.FALSE);     
    } else {
      userChoose.contextPut("showGroup", Boolean.TRUE);     
      userChoose.contextPut("groupName", StringEscapeUtils.escapeHtml(group.getName()));     
    }
   
    userChoose.put("userlisttable", userListCtr.getInitialComponent());
    // set main vc to userchoose
    setContent(userChoose);
  }

  private void doSimpleUserChoose(UserRequest ureq, List<Identity> identities) {
    // Init table headers
    removeAsListenerAndDispose(userListCtr);
    TableGuiConfiguration tableConfig = new TableGuiConfiguration();
    tableConfig.setPreferencesOffered(true, "assessmentSimpleUserList");
    tableConfig.setTableEmptyMessage(translate("userchoose.nousers"));   
   
    userListCtr = UserControllerFactory.createTableControllerFor(tableConfig, identities, ureq, getWindowControl(), null, CMD_CHOOSE_USER);
    listenTo(userListCtr);

    userChoose.contextPut("showBack", Boolean.FALSE);
    userChoose.contextPut("showGroup", Boolean.FALSE);     
   
    userChoose.put("userlisttable", userListCtr.getInitialComponent());
    // set main vc to userchoose
    setContent(userChoose);
  }

  private void doNodeChoose(UserRequest ureq) {
    ICourse course = CourseFactory.loadCourse(ores);
    removeAsListenerAndDispose(nodeListCtr);
    // table configuraton
    TableGuiConfiguration tableConfig = new TableGuiConfiguration();
    tableConfig.setTableEmptyMessage(translate("nodesoverview.nonodes"));
    tableConfig.setDownloadOffered(false);
    tableConfig.setColumnMovingOffered(false);
    tableConfig.setSortingEnabled(false);
    tableConfig.setDisplayTableHeader(true);
    tableConfig.setDisplayRowCount(false);
    tableConfig.setPageingEnabled(false);
   
    nodeListCtr = new TableController(tableConfig, ureq, getWindowControl(), getTranslator(), null);
    listenTo(nodeListCtr);
    // table columns   
    nodeListCtr.addColumnDescriptor(new CustomRenderColumnDescriptor("table.header.node", 0,
        null, ureq.getLocale(), ColumnDescriptor.ALIGNMENT_LEFT, new IndentedNodeRenderer()));
    nodeListCtr.addColumnDescriptor(new DefaultColumnDescriptor("table.action.select", 1,
        CMD_SELECT_NODE, ureq.getLocale()));
   
    // get list of course node data and populate table data model
    CourseNode rootNode = course.getRunStructure().getRootNode();   
    List<Map<String, Object>> nodesTableObjectArrayList = addAssessableNodesAndParentsToList(0, rootNode);
   
    // only populate data model if data available
    if (nodesTableObjectArrayList == null) {
      String text = translate("nodesoverview.nonodes");
      Controller messageCtr = MessageUIFactory.createSimpleMessage(ureq, getWindowControl(), text);
      listenTo(messageCtr);//dispose if this one gets disposed
      nodeChoose.put("nodeTable", messageCtr.getInitialComponent());
    } else {
      nodeTableModel = new NodeTableDataModel(nodesTableObjectArrayList, getTranslator());
      nodeListCtr.setTableDataModel(nodeTableModel);
      nodeChoose.put("nodeTable", nodeListCtr.getInitialComponent());
    }
   
    // set main content to nodechoose, do not use wrapper
    main.setContent(nodeChoose);
  }
 
  private void doBulkChoose(UserRequest ureq) {
    ICourse course = CourseFactory.loadCourse(ores);
    List<Identity> allowedIdentities = getAllIdentitisFromGroupmanagement();
    removeAsListenerAndDispose(bamc);
    bamc = new BulkAssessmentMainController(ureq, getWindowControl(), course, allowedIdentities);
    listenTo(bamc);
    main.setContent(bamc.getInitialComponent());
  }

  /**
   * Recursive method that adds assessable nodes and all its parents to a list
   * @param recursionLevel
   * @param courseNode
   * @return A list of maps containing the node data
   */
  private List<Map<String, Object>> addAssessableNodesAndParentsToList(int recursionLevel, CourseNode courseNode) {
    // 1) Get list of children data using recursion of this method
    List<Map<String, Object>> childrenData = new ArrayList<Map<String, Object>>();
    for (int i = 0; i < courseNode.getChildCount(); i++) {
      CourseNode child = (CourseNode) courseNode.getChildAt(i);
      List<Map<String, Object>> childData = addAssessableNodesAndParentsToList( (recursionLevel + 1),  child);
      if (childData != null)
        childrenData.addAll(childData);
    }
   
    boolean hasDisplayableValuesConfigured = false;
    if (childrenData.size() > 0 || courseNode instanceof AssessableCourseNode) {
      // Store node data in hash map. This hash map serves as data model for
      // the user assessment overview table. Leave user data empty since not used in
      // this table. (use only node data)
      Map<String,Object> nodeData = new HashMap<String, Object>();
      // indent
      nodeData.put(AssessmentHelper.KEY_INDENT, new Integer(recursionLevel));
      // course node data
      nodeData.put(AssessmentHelper.KEY_TYPE, courseNode.getType());
      nodeData.put(AssessmentHelper.KEY_TITLE_SHORT, courseNode.getShortTitle());
      nodeData.put(AssessmentHelper.KEY_TITLE_LONG, courseNode.getLongTitle());
      nodeData.put(AssessmentHelper.KEY_IDENTIFYER, courseNode.getIdent());
     
      if (courseNode instanceof AssessableCourseNode) {
        AssessableCourseNode assessableCourseNode = (AssessableCourseNode) courseNode;
        if ( assessableCourseNode.hasDetails()
          || assessableCourseNode.hasAttemptsConfigured()
          || assessableCourseNode.hasScoreConfigured()
          || assessableCourseNode.hasPassedConfigured()
          || assessableCourseNode.hasCommentConfigured()) {
          hasDisplayableValuesConfigured = true;
        }
        if (assessableCourseNode.isEditableConfigured()) {
          // Assessable course nodes are selectable when they are aditable
          nodeData.put(AssessmentHelper.KEY_SELECTABLE, Boolean.TRUE);
        } else if (courseNode instanceof STCourseNode
            && (assessableCourseNode.hasScoreConfigured()
            || assessableCourseNode.hasPassedConfigured())) {
          // st node is special case: selectable on node choose list as soon as it
          // has score or passed
          nodeData.put(AssessmentHelper.KEY_SELECTABLE, Boolean.TRUE);
        } else {
          // assessable nodes that do not have score or passed are not selectable
          // (e.g. a st node with no defined rule
          nodeData.put(AssessmentHelper.KEY_SELECTABLE, Boolean.FALSE);
        }
      } else {
        // Not assessable nodes are not selectable. (e.g. a node that
        // has an assessable child node but is itself not assessable)
        nodeData.put(AssessmentHelper.KEY_SELECTABLE, Boolean.FALSE);
      }
      // 3) Add data of this node to mast list if node assessable or children list has any data.
      // Do only add nodes when they have any assessable element, otherwhise discard (e.g. empty course,
      // structure nodes without scoring rules)! When the discardEmptyNodes flag is set then only
      // add this node when there is user data found for this node.
      if (childrenData.size() > 0 || hasDisplayableValuesConfigured) {
        List<Map<String, Object>> nodeAndChildren = new ArrayList<Map<String, Object>>();
        nodeAndChildren.add(nodeData);
        // 4) Add children data list to master list
        nodeAndChildren.addAll(childrenData);
        return nodeAndChildren;
      }
    }
    return null;
  }

  /**
   * Recursive method to add all assessable course nodes to a list
   * @param courseNode
   * @return List of course Nodes
   */
  private List addAssessableNodesToList(CourseNode courseNode, BusinessGroup group) {
    List result = new ArrayList();   
    if (courseNode instanceof AssessableCourseNode) {
      AssessableCourseNode assessableCourseNode = (AssessableCourseNode) courseNode;
      if ( assessableCourseNode.hasDetails()
        || assessableCourseNode.hasAttemptsConfigured()
        || assessableCourseNode.hasScoreConfigured()
        || assessableCourseNode.hasPassedConfigured()
        || assessableCourseNode.hasCommentConfigured()) {
        if ( !isFiltering || isVisibleAndAccessable(assessableCourseNode,group) ) {
          result.add(assessableCourseNode);
        }
      }
    }
    for (int i = 0; i < courseNode.getChildCount(); i++) {
      CourseNode child = (CourseNode) courseNode.getChildAt(i);
      result.addAll(addAssessableNodesToList(child, group));
    }
    return result;
  }
 
  /**
   * Check if a course node is visiable and accessibale for certain group.
   * Because the condition-interpreter works with identities, take the frist
   * identity from list of participants.
   *
   * @param courseNode
   * @param group
   * @return
   */
  private boolean isVisibleAndAccessable(CourseNode courseNode, BusinessGroup group) {
    if ( (courseNode == null) || ( group == null ) ) {
      return true;
    }
    if (getGroupIdentitiesFromGroupmanagement(group).size()==0) {
      // group has no participant, can not evalute 
      return false;
    }
    ICourse course = CourseFactory.loadCourse(ores);
    // check if course node is visible for group
    // get first identity to use this identity for condition interpreter
    Identity identity = getGroupIdentitiesFromGroupmanagement(group).get(0);
    IdentityEnvironment identityEnvironment = new IdentityEnvironment();
    identityEnvironment.setIdentity(identity);
    UserCourseEnvironment uce = new UserCourseEnvironmentImpl(identityEnvironment, course.getCourseEnvironment());
    OnlyGroupConditionInterpreter ci = new OnlyGroupConditionInterpreter(uce);
    List listOfConditionExpressions = courseNode.getConditionExpressions();
    boolean allConditionAreValid = true;
    // loop over all conditions, all must be true
    for (Iterator iter = listOfConditionExpressions.iterator(); iter.hasNext();) {
      ConditionExpression conditionExpression = (ConditionExpression) iter.next();
      Tracing.logDebug("conditionExpression=" + conditionExpression, this.getClass());
      Tracing.logDebug("conditionExpression.getId()=" + conditionExpression.getId(), this.getClass());
      Condition condition = new Condition();
      condition.setConditionId(conditionExpression.getId());
      condition.setConditionExpression(conditionExpression.getExptressionString());
      if ( !ci.evaluateCondition(condition) ) {
        allConditionAreValid = false;
      }
    }
    return allConditionAreValid;
  }
 
  /**
   * @param content Content to put in wrapper and set to main
   */
  private void setContent(Component content) {
    if (this.currentCourseNode == null) {
      wrapper.contextRemove("courseNode");
    } else {
      wrapper.contextPut("courseNode", this.currentCourseNode);
      // push node css class
      wrapper.contextPut("courseNodeCss", CourseNodeFactory.getInstance().getCourseNodeConfiguration(currentCourseNode.getType()).getIconCSSClass());

    }
    wrapper.put("content", content);
    main.setContent(wrapper);
  }
 
  /**
   * @param hasAssessableNodes true: show menu, false: hide menu
   * @return The tree model
   */
  private TreeModel buildTreeModel(boolean hasAssessableNodes) {
    GenericTreeNode root, gtn;

    GenericTreeModel gtm = new GenericTreeModel();
    root = new GenericTreeNode();
    root.setTitle(translate("menu.index"));
    root.setUserObject(CMD_INDEX);
    root.setAltText(translate("menu.index.alt"));
    gtm.setRootNode(root);

    // show real menu only when there are some assessable nodes
    if (hasAssessableNodes) { 
      gtn = new GenericTreeNode();
      gtn.setTitle(translate("menu.groupfocus"));
      gtn.setUserObject(CMD_GROUPFOCUS);
      gtn.setAltText(translate("menu.groupfocus.alt"));
      root.addChild(gtn);
 
      gtn = new GenericTreeNode();
      gtn.setTitle(translate("menu.nodefocus"));
      gtn.setUserObject(CMD_NODEFOCUS);
      gtn.setAltText(translate("menu.nodefocus.alt"));
      root.addChild(gtn);
     
      gtn = new GenericTreeNode();
      gtn.setTitle(translate("menu.userfocus"));
      gtn.setUserObject(CMD_USERFOCUS);
      gtn.setAltText(translate("menu.userfocus.alt"));
      root.addChild(gtn);

      gtn = new GenericTreeNode();
      gtn.setTitle(translate("menu.bulkfocus"));
      gtn.setUserObject(CMD_BULKFOCUS);
      gtn.setAltText(translate("menu.bulkfocus.alt"));
      root.addChild(gtn);
    }
   
    return gtm;
  }

  /**
   * @see org.olat.core.gui.control.DefaultController#doDispose(boolean)
   */
  protected void doDispose() {
    // controllers disposed by BasicController
      toolC = null;
      userListCtr = null;
    nodeListCtr = null;
      groupListCtr = null;
      csc = null;
    assessmentEditController = null;
    identityAssessmentController = null;
   
    // deregister from assessment changed events
    ICourse course = CourseFactory.loadCourse(ores);
    course.getCourseEnvironment().getAssessmentManager().deregisterFromAssessmentChangeEvents(this);

    // stop assessment cache preloader thread if still running
    if (assessmentCachPreloaderThread != null && assessmentCachPreloaderThread.isAlive()) {
      assessmentCachPreloaderThread.interrupt();
      if (log.isDebug()) {
        log.debug("Interrupting assessment cache preload in course::" + course.getResourceableId() + " while in doDispose()");
      }
    }
  }
 
  /**
   * Release resources used by child controllers. This must be called
   * to release locks produced by certain child controllers and to help the
   * garbage collector.
   */
  private void disposeChildControllerAndReleaseLocks() {
    removeAsListenerAndDispose(assessmentEditController);
    assessmentEditController = null;
    removeAsListenerAndDispose(identityAssessmentController);
    identityAssessmentController = null;
    }   

  /**
   * Description:<BR>
   * Thread that preloads the assessment cache and the user environment cache
   * <P>
   * Initial Date:  Mar 2, 2005
   *
   * @author gnaegi
   */
  class AssessmentCachePreloadThread extends Thread {
    /**
     * @param name Thread name
     */
    AssessmentCachePreloadThread(String name) {
      super(name);
    }

    /**
     * @see java.lang.Runnable#run()
     */
    public void run() {
      boolean success = false;
      try{
        ICourse course = CourseFactory.loadCourse(ores);
        // 1) preload assessment cache with database properties
        long start = 0;
        boolean logDebug = log.isDebug();
        if(logDebug) start = System.currentTimeMillis();
        course.getCourseEnvironment().getAssessmentManager().preloadCache();
        // 2) preload controller local user environment cache
        start = System.currentTimeMillis();
        List<Identity> identities = getAllIdentitisFromGroupmanagement();
        for (Iterator<Identity> iter = identities.iterator(); iter.hasNext();) {
          Identity identity = iter.next();
          AssessmentHelper.wrapIdentity(identity, localUserCourseEnvironmentCache, course, null);
          if (Thread.interrupted()) break;
        }
        if (logDebug) {
          log.debug("Preloading of user course environment cache for course::" + course.getResourceableId() + " for "
              + localUserCourseEnvironmentCache.size() + " user course environments. Loading time::" + (System.currentTimeMillis() - start)
              + "ms");
        }
        // TODO: cg(04.09.2008): replace 'commit/closeSession' with doInManagedBlock
        // finished in this thread, close database session of this thread!
        DBFactory.getInstance(false).commitAndCloseSession();
        success = true;
      } finally {
        if (!success) {
          DBFactory.getInstance(false).rollbackAndCloseSession();
        }
      }
    }
  }
 
  /**
   *
   * @param ureq
   * @param viewIdentifier if 'node-choose' does activate node-choose view
   */
  public void activate(UserRequest ureq, String viewIdentifier) {
    if (viewIdentifier != null && viewIdentifier.equals("node-choose")) {
      // jump to state node-choose
      doNodeChoose(ureq);
    }
  }
}
TOP

Related Classes of org.olat.course.assessment.AssessmentMainController

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.