/** * 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. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. */ package org.olat.course.nodes.ta; import java.io.File; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Random; import org.olat.core.commons.modules.bc.FolderConfig; import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; 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.BooleanColumnDescriptor; import org.olat.core.gui.components.table.DefaultColumnDescriptor; import org.olat.core.gui.components.table.DefaultTableDataModel; 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.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.BasicController; import org.olat.core.gui.util.CSSHelper; import org.olat.core.id.Identity; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.StringHelper; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSMediaResource; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.TACourseNode; import org.olat.course.properties.CoursePropertyManager; import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.scoring.AssessmentEvaluation; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.modules.ModuleConfiguration; import org.olat.modules.assessment.model.AssessmentEntryStatus; import org.olat.properties.Property; /** * Initial Date: 02.09.2004 * * @author Mike Stock * * Comment: * */ public class TaskController extends BasicController { private OLog log = Tracing.createLoggerFor(this.getClass()); private static final String ACTION_PREVIEW = "ta.preview"; private static final String ACTION_SELECT = "seltask"; private static final String ACTION_DESELECT = "deseltask"; private static final String VC_NOMORETASKS = "nomoretasks"; private static final String VC_ASSIGNEDTASK = "assignedtask"; private static final String VC_ASSIGNEDTASK_NEWWINDOW = "newwindow"; private static final String VC_TASKTEXT = "taskText"; static final String PROP_SAMPLED = "smpl"; /** Property key for "assigned" property. */ public static final String PROP_ASSIGNED = "ass"; /** Configuration parameter indicating manual selection task type. */ public static final String TYPE_MANUAL = "manual"; /** Configuration parameter indicating auto selection task type. */ public static final String TYPE_AUTO = "auto"; /** Configuration parameter indicating task-preview mode. */ public static final String WITH_PREVIEW = "preview"; /** Configuration parameter indicating non task-preview mode. */ public static final String WITHOUT_PREVIEW = "no_preview"; /** Configuration parameter indicating task-deselect mode. */ public static final String WITH_DESELECT = "deselect"; /** Configuration parameter indicating non task-deselect mode. */ public static final String WITHOUT_DESELECT = "no_deselect"; // config private String taskType; private String taskText; private File taskFolder; private boolean samplingWithReplacement = true; private Boolean hasPreview = Boolean.FALSE; private Boolean isDeselectable = Boolean.FALSE; private TACourseNode node; private CourseEnvironment courseEnv; private UserCourseEnvironment userCourseEnv; private VelocityContainer myContent; private Link taskLaunchButton; private TableController tableCtr; private DeselectableTaskTableModel taskTableModel; private String assignedTask; private Panel panel; /** * Implements a task component. * @param ureq * @param wControl * @param config * @param node * @param courseEnv */ public TaskController(UserRequest ureq, WindowControl wControl, ModuleConfiguration config, TACourseNode node, UserCourseEnvironment userCourseEnv) { super(ureq, wControl); this.node = node; courseEnv = userCourseEnv.getCourseEnvironment(); this.userCourseEnv = userCourseEnv; readConfig(config); panel = new Panel("myContentPanel"); myContent = createVelocityContainer("taskAssigned"); taskLaunchButton = LinkFactory.createButtonXSmall("task.launch", myContent, this); taskLaunchButton.setTarget("_blank"); taskLaunchButton.setAjaxEnabled(false); // opened in new window if (StringHelper.containsNonWhitespace(taskText)) { myContent.contextPut(VC_TASKTEXT, taskText); } // check if user already chose a task assignedTask = getAssignedTask(ureq.getIdentity(), courseEnv, node); if (assignedTask != null && !isDeselectable()) { // pushTaskToVC(); } else { // prepare choose task if (taskType.equals(TYPE_AUTO)) { // automatically choose a task assignedTask = assignTask(ureq.getIdentity()); if (assignedTask != null) { pushTaskToVC(); } else { myContent.contextPut(VC_NOMORETASKS, translate("task.nomoretasks")); panel.setContent(myContent); } } else { // let user choose a task, or show the table with the available/selected task myContent = createVelocityContainer("taskChoose"); List<String> availableTasks = compileAvailableTasks(); if (availableTasks.size() == 0 && assignedTask==null) { // no more tasks available myContent.contextPut(VC_NOMORETASKS, translate("task.nomoretasks")); } else { TableGuiConfiguration tableConfig = new TableGuiConfiguration(); tableCtr = new TableController(tableConfig, ureq, wControl, getTranslator()); listenTo(tableCtr); // No Preview Mode, Show only file-name tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("task.table.th_task", 0, null, ureq.getLocale())); if ( (hasPreview != null) && (hasPreview.booleanValue()==true )) { // Preview Mode DefaultColumnDescriptor columnDescriptor = new DefaultColumnDescriptor("task.table.th_task", 1, ACTION_PREVIEW, ureq.getLocale()); columnDescriptor.setIsPopUpWindowAction(true,DefaultColumnDescriptor.DEFAULT_POPUP_ATTRIBUTES ); tableCtr.addColumnDescriptor(columnDescriptor); } //always have a select column String selectCmd = userCourseEnv.isCourseReadOnly() ? null : ACTION_SELECT; tableCtr.addColumnDescriptor(new BooleanColumnDescriptor("task.table.th_action", 2, selectCmd, translate("task.table.choose"), "-")); int numCols = 0; Boolean taskCouldBeDeselected = config.getBooleanEntry(TACourseNode.CONF_TASK_DESELECT); if(!hasPreview) { numCols = 2; } else if (taskCouldBeDeselected==null || !taskCouldBeDeselected){ numCols = 3; } else if (taskCouldBeDeselected) { numCols = 4; String deselectCmd = userCourseEnv.isCourseReadOnly() ? null : ACTION_DESELECT; tableCtr.addColumnDescriptor(new BooleanColumnDescriptor("task.table.th_deselect",3, deselectCmd, translate("task.table.deselect"), "-")); } //the table model shows the available tasks, plus the selected one, if deselectable if(isDeselectable() && assignedTask!=null && !availableTasks.contains(assignedTask)) { availableTasks.add(assignedTask); } taskTableModel = new DeselectableTaskTableModel(availableTasks, numCols); tableCtr.setTableDataModel(taskTableModel); myContent.put("taskTable", tableCtr.getInitialComponent()); } } } panel.setContent(myContent); putInitialPanel(panel); } /** * @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) { log.debug("Test Component.event source" + source + " , event=" + event); if (source == taskLaunchButton) { //deliver files the same way as in preview doFileDelivery(ureq, assignedTask); } } /** * @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) { log.debug("Test Controller.event source" + source + " , event=" + event); if (source == tableCtr) { if (event.getCommand().equals(Table.COMMANDLINK_ROWACTION_CLICKED)) { TableEvent ta = (TableEvent) event; if (ta.getActionId().equals(TaskController.ACTION_PREVIEW)) { String previewTask = (String)taskTableModel.getValueAt(ta.getRowId(),0); doFileDelivery(ureq, previewTask); } else if (ta.getActionId().equals(TaskController.ACTION_SELECT)){ // select a task assignedTask = (String)taskTableModel.getValueAt(ta.getRowId(),0); List<String> availableTasks = compileAvailableTasks(); if (!availableTasks.contains(assignedTask)) { showWarning("task.chosen"); taskTableModel.setObjects(availableTasks); tableCtr.modelChanged(); if (availableTasks.size() == 0) { // no more tasks available myContent.contextPut(VC_NOMORETASKS, translate("task.nomoretasks")); } } else { setAssignedTask(ureq.getIdentity(), assignedTask); if (!samplingWithReplacement) markTaskAsSampled(assignedTask); if(!isDeselectable()) { pushTaskToVC(); } else { //if assignedTask selected, and deselectable, update taskTableModel List<String> allTasks = compileAvailableTasks(); if(!samplingWithReplacement) { //if assignable to only one user, this means that the assignedTask is no more in the availableTasks, but show it in taskTableModel allTasks.add(assignedTask); } taskTableModel.setObjects(allTasks); tableCtr.modelChanged(); } } } else if (ta.getActionId().equals(TaskController.ACTION_DESELECT)) { if(assignedTask!=null) { removeAssignedTask(ureq.getIdentity()); List<String> availableTasks = compileAvailableTasks(); taskTableModel.setObjects(availableTasks); tableCtr.modelChanged(); } } } } } private void pushTaskToVC() { if (assignedTask == null) { return; } myContent = createVelocityContainer ("taskAssigned"); if (StringHelper.containsNonWhitespace(taskText)) { myContent.contextPut(VC_TASKTEXT, taskText); } //String taskFilename = getTaskFilename(assignedTask); //taskLaunchButton.setModURI(taskFilename); myContent.put("task.launch", taskLaunchButton); myContent.contextPut(VC_ASSIGNEDTASK, assignedTask); myContent.contextPut(VC_ASSIGNEDTASK_NEWWINDOW,Boolean.TRUE); myContent.contextPut("taskIcon", CSSHelper.createFiletypeIconCssClassFor(assignedTask)); panel.setContent(myContent); } /*private String getTaskFilename(String task) { if(!StringHelper.containsNonWhitespace(task)) { return null; } String extension = FileUtils.getFileSuffix(assignedTask); if(!StringHelper.containsNonWhitespace(extension)) { return null; } String filename = assignedTask.substring(0, assignedTask.length() - extension.length()); return StringHelper.transformDisplayNameToFileSystemName(filename) + "." + extension; }*/ /** * Auto-assign a task to an identity and mark it as sampled if necessary. * @param identity * @return name of the assigned task or null if no more tasks are available. */ private String assignTask(Identity identity) { List<String> availableTasks = compileAvailableTasks(); if (availableTasks.size() == 0 && samplingWithReplacement) { unmarkAllSampledTasks(); // unmark all tasks if samplingWithReplacement and no more tasks available availableTasks = compileAvailableTasks(); // refetch tasks } if (availableTasks.size() == 0) return null; // no more task available String task = availableTasks.get((new Random()).nextInt(availableTasks.size())); setAssignedTask(identity, task); // assignes the file to this identity if (!samplingWithReplacement) markTaskAsSampled(task); // remove the file from available files return task; } public static String getAssignedTask(Identity identity, CourseEnvironment courseEnv, CourseNode node) { List<Property> samples = courseEnv.getCoursePropertyManager().findCourseNodeProperties(node, identity, null, PROP_ASSIGNED); if (samples.size() == 0) return null; // no sample assigned yet return samples.get(0).getStringValue(); } private void setAssignedTask(Identity identity, String task) { CoursePropertyManager cpm = courseEnv.getCoursePropertyManager(); Property p = cpm.createCourseNodePropertyInstance(node, identity, null, PROP_ASSIGNED, null, null, task, null); cpm.saveProperty(p); AssessmentEvaluation eval = node.getUserScoreEvaluation(userCourseEnv); if(eval.getAssessmentStatus() == null || eval.getAssessmentStatus() == AssessmentEntryStatus.notStarted) { eval = new AssessmentEvaluation(eval, AssessmentEntryStatus.inProgress); node.updateUserScoreEvaluation(eval, userCourseEnv, getIdentity(), false); } } /** * Cancel the task assignment. * @param identity * @param task */ private void removeAssignedTask(Identity identity) { CoursePropertyManager cpm = courseEnv.getCoursePropertyManager(); //remove assigned List<Property> properties = cpm.findCourseNodeProperties(node, identity, null, PROP_ASSIGNED); if(properties!=null && properties.size()>0) { Property propety = properties.get(0); cpm.deleteProperty(propety); assignedTask = null; } //removed sampled properties = courseEnv.getCoursePropertyManager().findCourseNodeProperties(node, null, null, PROP_SAMPLED); if(properties!=null && properties.size()>0) { Property propety = properties.get(0); cpm.deleteProperty(propety); } } private void markTaskAsSampled(String task) { CoursePropertyManager cpm = courseEnv.getCoursePropertyManager(); Property p = cpm.createCourseNodePropertyInstance(node, null, null, PROP_SAMPLED, null, null, task, null); cpm.saveProperty(p); } private void unmarkAllSampledTasks() { courseEnv.getCoursePropertyManager().deleteNodeProperties(node, PROP_SAMPLED); } /** * Compiles a list of tasks based on the available files in the task folder, * which have not been sampled so far. * @return List of available tasks. */ private List<String> compileAvailableTasks() { File[] taskSources = taskFolder.listFiles(); List<String> tasks = new ArrayList<String>(taskSources.length); List<String> sampledTasks = compileSampledTasks(); for (int i = 0; i < taskSources.length; i++) { File nextTask = taskSources[i]; if (nextTask.isFile() && !sampledTasks.contains(nextTask.getName())) tasks.add(nextTask.getName()); } return tasks; } /** * Compile a list of tasks marked as sampled. * @return List of sampled tasks. */ private List<String> compileSampledTasks() { List<String> sampledTasks = new ArrayList<String>(); List<Property> samples = courseEnv.getCoursePropertyManager() .findCourseNodeProperties(node, null, null, PROP_SAMPLED); for (Iterator<Property> iter = samples.iterator(); iter.hasNext();) { Property sample = iter.next(); sampledTasks.add(sample.getStringValue()); } return sampledTasks; } private void readConfig(ModuleConfiguration config) { // get task type taskType = (String)config.get(TACourseNode.CONF_TASK_TYPE); if (!(taskType.equals(TYPE_MANUAL) || taskType.equals(TYPE_AUTO))) throw new AssertException("Invalid task type: " + taskType); // get folder path String sTaskFolder = FolderConfig.getCanonicalRoot() + TACourseNode.getTaskFolderPathRelToFolderRoot(courseEnv, node); taskFolder = new File(sTaskFolder); if (!taskFolder.exists() && !taskFolder.mkdirs()) throw new AssertException("Task folder " + sTaskFolder + " does not exist."); // get sampling type Boolean bSampling = (Boolean)config.get(TACourseNode.CONF_TASK_SAMPLING_WITH_REPLACEMENT); samplingWithReplacement = (bSampling == null) ? true : bSampling.booleanValue(); // get task introductory text taskText = (String)config.get(TACourseNode.CONF_TASK_TEXT); hasPreview = config.getBooleanEntry(TACourseNode.CONF_TASK_PREVIEW)==null ? false : config.getBooleanEntry(TACourseNode.CONF_TASK_PREVIEW); isDeselectable = config.getBooleanEntry(TACourseNode.CONF_TASK_DESELECT)==null ? false : config.getBooleanEntry(TACourseNode.CONF_TASK_DESELECT); } private boolean isDeselectable() { return isDeselectable; } /** * * @see org.olat.core.gui.control.DefaultController#doDispose(boolean) */ protected void doDispose() { // } /** * deliver the selected file and show in a popup tableController* * @param ureq * @param command */ private boolean doFileDelivery(UserRequest ureq, String taskFile) { OlatRootFolderImpl forumContainer = new OlatRootFolderImpl(TACourseNode.getTaskFolderPathRelToFolderRoot(courseEnv, node), null); VFSItem item = forumContainer.resolve(taskFile); if (item instanceof VFSLeaf) { VFSLeaf leaf = (VFSLeaf)item; ureq.getDispatchResult().setResultingMediaResource(new VFSMediaResource(leaf)); return true; } else if (item==null) { log.warn("Can not cast to VFSLeaf. item==null, taskFile=" + taskFile); return false; } else { log.warn("Can not cast to VFSLeaf. item.class.name=" + item.getClass().getName() + ", taskFile="+taskFile); return false; } } /** * * Description:<br> * Model holding available tasks. * Contains 4 cols: task title, view, select, and deselect. * * <P> * Initial Date: 20.04.2010 <br> * @author Lavinia Dumitrescu */ class DeselectableTaskTableModel extends DefaultTableDataModel<String> { private int COLUMN_COUNT; public DeselectableTaskTableModel(List<String> objects, int num_cols) { super(objects); COLUMN_COUNT = num_cols; } @Override public int getColumnCount() { return COLUMN_COUNT; } @Override public Object getValueAt(int row, int col) { String taskTitle = objects.get(row); if (col == 0) { return taskTitle; } else if (col == 1) { return "View"; } else if (col == 2) { return new Boolean(!hasAnyTaskAssigned()); } else if (col == 3) { return new Boolean(isTaskAssigned(taskTitle)); } return "ERROR"; } private boolean hasAnyTaskAssigned() { return assignedTask!=null; } private boolean isTaskAssigned(String task) { return task.equals(assignedTask); } } }