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