From 88abd372574fa7f8c8f5cb4c38f5a0fb805b9ed3 Mon Sep 17 00:00:00 2001
From: uhensler <none@none>
Date: Wed, 4 Oct 2017 08:26:29 +0200
Subject: [PATCH] OO-3024: Tab favorites in the course node "task". Download of
 all documents of the favorited participants.

---
 .../org/olat/course/nodes/gta/GTAManager.java |   4 +
 .../olat/course/nodes/gta/IdentityMark.java   |  49 +++++
 .../nodes/gta/manager/GTAIdentityMarkDAO.java | 108 ++++++++++
 .../nodes/gta/manager/GTAManagerImpl.java     |  31 ++-
 .../nodes/gta/model/IdentityMarkImpl.java     | 178 ++++++++++++++++
 .../gta/ui/CoachParticipantsTableModel.java   |   5 +-
 .../nodes/gta/ui/CoachedIdentityRow.java      |  12 +-
 .../gta/ui/GTACoachSelectionController.java   |  52 +++--
 .../GTACoachedParticipantListController.java  |  52 ++++-
 .../course/nodes/gta/ui/GTARunController.java |  60 +++++-
 .../gta/ui/_content/coach_selection.html      |   6 +-
 .../gta/ui/_i18n/LocalStrings_de.properties   |   4 +-
 .../gta/ui/_i18n/LocalStrings_en.properties   |   4 +-
 .../gta/ui/_i18n/LocalStrings_fr.properties   |   2 +-
 .../gta/ui/_i18n/LocalStrings_it.properties   |   2 +-
 .../gta/ui/_i18n/LocalStrings_pl.properties   |   2 +-
 .../ui/_i18n/LocalStrings_pt_BR.properties    |   2 +-
 .../_spring/databaseUpgradeContext.xml        |   4 +
 src/main/resources/META-INF/persistence.xml   |   1 +
 .../database/mysql/alter_12_1_x_to_12_2_0.sql |  14 ++
 .../database/mysql/setupDatabase.sql          |  13 ++
 .../oracle/alter_12_1_x_to_12_2_0.sql         |  12 ++
 .../database/oracle/setupDatabase.sql         |  13 ++
 .../postgresql/alter_12_1_x_to_12_2_0.sql     |  12 ++
 .../database/postgresql/setupDatabase.sql     |  13 ++
 .../gta/manager/GTAIdentityMarkDAOTest.java   | 190 ++++++++++++++++++
 .../java/org/olat/test/AllTestsJunit4.java    |   1 +
 27 files changed, 807 insertions(+), 39 deletions(-)
 create mode 100644 src/main/java/org/olat/course/nodes/gta/IdentityMark.java
 create mode 100644 src/main/java/org/olat/course/nodes/gta/manager/GTAIdentityMarkDAO.java
 create mode 100644 src/main/java/org/olat/course/nodes/gta/model/IdentityMarkImpl.java
 create mode 100644 src/main/resources/database/mysql/alter_12_1_x_to_12_2_0.sql
 create mode 100644 src/main/resources/database/oracle/alter_12_1_x_to_12_2_0.sql
 create mode 100644 src/main/resources/database/postgresql/alter_12_1_x_to_12_2_0.sql
 create mode 100644 src/test/java/org/olat/course/nodes/gta/manager/GTAIdentityMarkDAOTest.java

diff --git a/src/main/java/org/olat/course/nodes/gta/GTAManager.java b/src/main/java/org/olat/course/nodes/gta/GTAManager.java
index eebc0e9c905..11581dedcef 100644
--- a/src/main/java/org/olat/course/nodes/gta/GTAManager.java
+++ b/src/main/java/org/olat/course/nodes/gta/GTAManager.java
@@ -353,6 +353,10 @@ public interface GTAManager {
 	
 	public Task resetTaskRefused(Task task, GTACourseNode cNode);
 	
+	public boolean toggleMark(RepositoryEntry entry, GTACourseNode gtaNode, Identity marker, Identity participant);
+
+	public List<IdentityMark> getMarks(RepositoryEntry entry, GTACourseNode gtaNode, Identity marker);
+
 	public void log(String step, String operation, Task assignedTask, Identity actor, Identity assessedIdentity, BusinessGroup assessedGroup,
 			CourseEnvironment courseEnv, GTACourseNode cNode);
 	
diff --git a/src/main/java/org/olat/course/nodes/gta/IdentityMark.java b/src/main/java/org/olat/course/nodes/gta/IdentityMark.java
new file mode 100644
index 00000000000..13eabed3f4f
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/gta/IdentityMark.java
@@ -0,0 +1,49 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.nodes.gta;
+
+import org.olat.core.id.CreateInfo;
+import org.olat.core.id.Identity;
+import org.olat.core.id.ModifiedInfo;
+
+/**
+ * 
+ * Initial date: 02.10.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public interface IdentityMark extends CreateInfo, ModifiedInfo {
+
+	public TaskList getTaskList();
+	
+	public void setTaskList(TaskList taskList);
+	
+	/**
+	 * The marker is the identity how has set the mark.
+	 */
+	public Identity getMarker();
+	
+	public void setMarker(Identity marker);
+	
+	public Identity getParticipant();
+	
+	public void setParticipant(Identity participant);
+	
+}
diff --git a/src/main/java/org/olat/course/nodes/gta/manager/GTAIdentityMarkDAO.java b/src/main/java/org/olat/course/nodes/gta/manager/GTAIdentityMarkDAO.java
new file mode 100644
index 00000000000..3c0042b897b
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/gta/manager/GTAIdentityMarkDAO.java
@@ -0,0 +1,108 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.nodes.gta.manager;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.Identity;
+import org.olat.course.nodes.gta.IdentityMark;
+import org.olat.course.nodes.gta.TaskList;
+import org.olat.course.nodes.gta.model.IdentityMarkImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 03.10.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class GTAIdentityMarkDAO {
+	
+	@Autowired
+	private DB dbInstance;
+
+	public IdentityMark createAndPersisitMark(TaskList taskList, Identity marker, Identity participant) {
+		IdentityMarkImpl mark = new IdentityMarkImpl();
+		Date creationDate = new Date();
+		mark.setCreationDate(creationDate);
+		mark.setLastModified(creationDate);
+		mark.setTaskList(taskList);
+		mark.setMarker(marker);
+		mark.setParticipant(participant);
+		dbInstance.getCurrentEntityManager().persist(mark);
+		return mark;	
+	}
+	
+	public List<IdentityMark> loadMarks(TaskList taskList, Identity marker) {
+		if (taskList == null || taskList.getKey() == null || marker == null || marker.getKey() == null)
+			return new ArrayList<>();
+
+		return dbInstance.getCurrentEntityManager()
+				.createNamedQuery("loadByMarker", IdentityMark.class)
+				.setParameter("taskListKey", taskList.getKey())
+				.setParameter("markerKey", marker.getKey())
+				.getResultList();
+	}
+	
+	public boolean isMarked(TaskList taskList, Identity marker, Identity participant) {
+		if (taskList == null || taskList.getKey() == null || marker == null || marker.getKey() == null
+				|| participant == null || participant.getKey() == null) {
+			return false;
+		}
+
+		List<IdentityMark> marks = dbInstance.getCurrentEntityManager()
+				.createNamedQuery("loadByMarkerAndParticipant", IdentityMark.class)
+				.setParameter("taskListKey", taskList.getKey())
+				.setParameter("markerKey", marker.getKey())
+				.setParameter("participantKey", participant.getKey())
+				.getResultList();
+		
+		return !marks.isEmpty();
+	}
+
+	public void deleteMark(TaskList taskList, Identity marker, Identity participant) {
+		if (taskList == null || taskList.getKey() == null || marker == null || marker.getKey() == null
+				|| participant == null || participant.getKey() == null) {
+			return;
+		}
+
+		dbInstance.getCurrentEntityManager()
+				.createNamedQuery("deleteByMarker")
+				.setParameter("taskListKey", taskList.getKey())
+				.setParameter("markerKey", marker.getKey())
+				.setParameter("participantKey", participant.getKey())
+				.executeUpdate();
+	}
+
+	public int deleteMark(TaskList taskList) {
+		if (taskList == null || taskList.getKey() == null) return 0;
+		
+		return dbInstance.getCurrentEntityManager()
+				.createNamedQuery("deleteByTaskList")
+				.setParameter("taskListKey", taskList.getKey())
+				.executeUpdate();
+	}
+
+}
diff --git a/src/main/java/org/olat/course/nodes/gta/manager/GTAManagerImpl.java b/src/main/java/org/olat/course/nodes/gta/manager/GTAManagerImpl.java
index d5ec9670112..e2dea18abe5 100644
--- a/src/main/java/org/olat/course/nodes/gta/manager/GTAManagerImpl.java
+++ b/src/main/java/org/olat/course/nodes/gta/manager/GTAManagerImpl.java
@@ -65,6 +65,7 @@ import org.olat.course.nodes.gta.AssignmentResponse.Status;
 import org.olat.course.nodes.gta.GTAManager;
 import org.olat.course.nodes.gta.GTARelativeToDates;
 import org.olat.course.nodes.gta.GTAType;
+import org.olat.course.nodes.gta.IdentityMark;
 import org.olat.course.nodes.gta.Task;
 import org.olat.course.nodes.gta.TaskDueDate;
 import org.olat.course.nodes.gta.TaskLight;
@@ -123,6 +124,8 @@ public class GTAManagerImpl implements GTAManager, DeletableGroupData {
 	@Autowired
 	private DB dbInstance;
 	@Autowired
+	private GTAIdentityMarkDAO gtaMarkDao;
+	@Autowired
 	private BGAreaManager areaManager;
 	@Autowired
 	private AssessmentService assessmentService;
@@ -751,8 +754,11 @@ public class GTAManagerImpl implements GTAManager, DeletableGroupData {
 			int numOfTasks = dbInstance.getCurrentEntityManager().createQuery(deleteTasks)
 				.setParameter("taskListKey", taskList.getKey())
 				.executeUpdate();
+			numOfDeletedObjects = numOfTasks;
+			int numOfMarks = gtaMarkDao.deleteMark(taskList);
+			numOfDeletedObjects += numOfMarks;
 			dbInstance.getCurrentEntityManager().remove(taskList);
-			numOfDeletedObjects = numOfTasks + 1;
+			numOfDeletedObjects++;
 		} else {
 			numOfDeletedObjects = 0;
 		}
@@ -773,6 +779,8 @@ public class GTAManagerImpl implements GTAManager, DeletableGroupData {
 		for(TaskList taskList:taskLists) {
 			int numOfTasks = deleteTaskQuery.setParameter("taskListKey", taskList.getKey()).executeUpdate();
 			numOfDeletedObjects += numOfTasks;
+			int numOfMarks = gtaMarkDao.deleteMark(taskList);
+			numOfDeletedObjects += numOfMarks;
 		}
 		
 		String deleteTaskLists = "delete from gtatasklist as tasks where tasks.entry.key=:entryKey";
@@ -1560,6 +1568,27 @@ public class GTAManagerImpl implements GTAManager, DeletableGroupData {
 		return taskImpl;
 	}
 
+	@Override
+	public boolean toggleMark(RepositoryEntry entry, GTACourseNode gtaNode, Identity marker, Identity participant) {
+		if (entry == null || gtaNode == null || marker == null || participant == null) return false;
+		
+		TaskList taskList = getTaskList(entry, gtaNode);
+		boolean isMarked = gtaMarkDao.isMarked(taskList, marker, participant);
+		if (isMarked) {
+			gtaMarkDao.deleteMark(taskList, marker, participant);
+		} else {
+			gtaMarkDao.createAndPersisitMark(taskList, marker, participant);
+		}
+		return !isMarked;
+		
+	}
+
+	@Override
+	public List<IdentityMark> getMarks(RepositoryEntry entry, GTACourseNode gtaNode, Identity marker) {
+		TaskList taskList = getTaskList(entry, gtaNode);
+		return gtaMarkDao.loadMarks(taskList, marker);
+	}
+
 	@Override
 	public AssessmentEntryStatus convertToAssessmentEntrystatus(Task task, GTACourseNode cNode) {
 		TaskProcess status = task.getTaskStatus();
diff --git a/src/main/java/org/olat/course/nodes/gta/model/IdentityMarkImpl.java b/src/main/java/org/olat/course/nodes/gta/model/IdentityMarkImpl.java
new file mode 100644
index 00000000000..d5b60bff09e
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/gta/model/IdentityMarkImpl.java
@@ -0,0 +1,178 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.nodes.gta.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.olat.basesecurity.IdentityImpl;
+import org.olat.core.id.Identity;
+import org.olat.core.id.Persistable;
+import org.olat.course.nodes.gta.IdentityMark;
+import org.olat.course.nodes.gta.TaskList;
+
+/**
+ * 
+ * Initial date: 02.10.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="gtaMark")
+@Table(name="o_gta_mark")
+@NamedQueries({
+	@NamedQuery(name="loadByMarker", query=
+			  "select mark"
+			+ "  from gtaMark mark"
+			+ " where mark.taskList.key=:taskListKey"
+			+ "   and mark.marker.key=:markerKey"),
+	@NamedQuery(name="loadByMarkerAndParticipant", query=
+			  "select mark"
+			+ "  from gtaMark mark"
+			+ " where mark.taskList.key=:taskListKey"
+			+ "   and mark.marker.key=:markerKey"
+			+ "   and mark.participant.key=:participantKey"),
+	@NamedQuery(name="deleteByMarker", query =
+			  "delete from gtaMark mark"
+			+ " where mark.taskList.key=:taskListKey"
+			+ "   and mark.marker.key=:markerKey"
+			+ "   and mark.participant.key=:participantKey"),
+	@NamedQuery(name="deleteByTaskList", query =
+			  "delete from gtaMark mark"
+			+ " where mark.taskList.key=:taskListKey")
+})
+public class IdentityMarkImpl implements IdentityMark, Persistable {
+
+	private static final long serialVersionUID = 5984891452227836907L;
+	
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="lastmodified", nullable=false, insertable=true, updatable=true)
+	private Date lastModified;
+	
+	@ManyToOne(targetEntity=TaskListImpl.class,fetch=FetchType.LAZY,optional=false)
+	@JoinColumn(name="fk_tasklist_id", nullable=false, insertable=true, updatable=false)
+	private TaskList taskList;
+	
+	@ManyToOne(targetEntity=IdentityImpl.class,fetch=FetchType.LAZY,optional=true)
+	@JoinColumn(name="fk_marker_identity_id", nullable=true, insertable=true, updatable=false)
+	private Identity marker;
+	@ManyToOne(targetEntity=IdentityImpl.class,fetch=FetchType.LAZY,optional=true)
+	@JoinColumn(name="fk_participant_identity_id", nullable=true, insertable=true, updatable=false)
+	private Identity participant;
+	
+	@Override
+	public Long getKey() {
+		return key;
+	}
+	
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+	
+	public void setCreationDate(Date creationDate) {
+		this.creationDate = creationDate;
+	}
+	
+	@Override
+	public Date getLastModified() {
+		return lastModified;
+	}
+	
+	@Override
+	public void setLastModified(Date lastModified) {
+		this.lastModified = lastModified;
+	}
+	
+	@Override
+	public TaskList getTaskList() {
+		return taskList;
+	}
+	
+	@Override
+	public void setTaskList(TaskList taskList) {
+		this.taskList = taskList;
+	}
+	
+	@Override
+	public Identity getMarker() {
+		return marker;
+	}
+	
+	@Override
+	public void setMarker(Identity marker) {
+		this.marker = marker;
+	}
+	
+	@Override
+	public Identity getParticipant() {
+		return participant;
+	}
+	
+	@Override
+	public void setParticipant(Identity participant) {
+		this.participant = participant;
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((key == null) ? 0 : key.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		} else if(obj instanceof IdentityMarkImpl) {
+			IdentityMarkImpl other = (IdentityMarkImpl)obj;
+			return getKey() != null && getKey().equals(other.getKey());
+		}
+		return false;
+	}
+	
+}
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/CoachParticipantsTableModel.java b/src/main/java/org/olat/course/nodes/gta/ui/CoachParticipantsTableModel.java
index d633a795238..4dd3b3882cb 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/CoachParticipantsTableModel.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/CoachParticipantsTableModel.java
@@ -68,7 +68,9 @@ public class CoachParticipantsTableModel extends DefaultFlexiTableDataModel<Coac
 	
 	@Override
 	public Object getValueAt(CoachedIdentityRow row, int col) {
-		if(col == CGCols.username.ordinal()) {
+		if(col == CGCols.mark.ordinal()) {
+			return row.getMarkLink();
+		} else if(col == CGCols.username.ordinal()) {
 			return row.getIdentity().getIdentityName();
 		} else if(col == CGCols.taskStatus.ordinal()) {
 			return row.getTaskStatus();
@@ -84,6 +86,7 @@ public class CoachParticipantsTableModel extends DefaultFlexiTableDataModel<Coac
 	}
 	
 	public enum CGCols {
+		mark("table.header.mark"),
 		username("username"),
 		taskName("table.header.group.taskName"),
 		taskStatus("table.header.group.step"),
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/CoachedIdentityRow.java b/src/main/java/org/olat/course/nodes/gta/ui/CoachedIdentityRow.java
index 5d8cea03f80..de4a80bad9f 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/CoachedIdentityRow.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/CoachedIdentityRow.java
@@ -21,6 +21,7 @@ package org.olat.course.nodes.gta.ui;
 
 import java.util.Date;
 
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
 import org.olat.course.nodes.gta.TaskLight;
 import org.olat.course.nodes.gta.TaskProcess;
 import org.olat.user.UserPropertiesRow;
@@ -33,6 +34,8 @@ import org.olat.user.UserPropertiesRow;
  */
 public class CoachedIdentityRow implements CoachedElementRow {
 
+	private FormLink markLink;
+	
 	private final TaskLight task;
 	private final Date submissionDueDate;
 	private final Date syntheticSubmissionDate;
@@ -40,12 +43,13 @@ public class CoachedIdentityRow implements CoachedElementRow {
 	private final UserPropertiesRow identity;
 	
 	public CoachedIdentityRow(UserPropertiesRow identity, TaskLight task, Date submissionDueDate,
-			Date syntheticSubmissionDate, boolean hasSubmittedDocuments) {
+			Date syntheticSubmissionDate, boolean hasSubmittedDocuments, FormLink markLink) {
 		this.identity = identity;
 		this.task = task;
 		this.submissionDueDate = submissionDueDate;
 		this.hasSubmittedDocuments = hasSubmittedDocuments;
 		this.syntheticSubmissionDate = syntheticSubmissionDate;
+		this.markLink = markLink;
 	}
 
 	@Override
@@ -96,4 +100,10 @@ public class CoachedIdentityRow implements CoachedElementRow {
 	public UserPropertiesRow getIdentity() {
 		return identity;
 	}
+
+	public FormLink getMarkLink() {
+		return markLink;
+	}
+
+	
 }
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachSelectionController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachSelectionController.java
index ca06397e1a2..07c9cb16410 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachSelectionController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachSelectionController.java
@@ -23,6 +23,7 @@ package org.olat.course.nodes.gta.ui;
 import java.io.File;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.olat.basesecurity.BaseSecurity;
 import org.olat.core.commons.services.notifications.PublisherData;
@@ -58,6 +59,7 @@ import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.course.run.userview.UserCourseEnvironmentImpl;
 import org.olat.group.BusinessGroup;
 import org.olat.modules.ModuleConfiguration;
+import org.olat.repository.RepositoryEntry;
 import org.olat.resource.OLATResource;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -82,9 +84,11 @@ public class GTACoachSelectionController extends BasicController implements Acti
 	private final GTACourseNode gtaNode;
 	private final CourseEnvironment courseEnv;
 	private final UserCourseEnvironment coachCourseEnv;
+	private final boolean markedOnly;
+	
+	protected PublisherData publisherData;
+	protected SubscriptionContext subsContext;
 	
-	protected final PublisherData publisherData;
-	protected final SubscriptionContext subsContext;
 	
 	@Autowired
 	private GTAManager gtaManager;
@@ -92,11 +96,12 @@ public class GTACoachSelectionController extends BasicController implements Acti
 	private BaseSecurity securityManager;
 	
 	public GTACoachSelectionController(UserRequest ureq, WindowControl wControl,
-			UserCourseEnvironment coachCourseEnv, GTACourseNode gtaNode) {
+			UserCourseEnvironment coachCourseEnv, GTACourseNode gtaNode, boolean markedOnly) {
 		super(ureq, wControl);
 		this.gtaNode = gtaNode;
 		this.coachCourseEnv = coachCourseEnv;
 		this.courseEnv = coachCourseEnv.getCourseEnvironment();
+		this.markedOnly = markedOnly;
 		
 		mainVC = createVelocityContainer("coach_selection");
 		backLink = LinkFactory.createLinkBack(mainVC, this);
@@ -109,12 +114,14 @@ public class GTACoachSelectionController extends BasicController implements Acti
 		downloadButton = LinkFactory.createButton("bulk.download.title", mainVC, this);
 		downloadButton.setTranslator(getTranslator());
 		
-		publisherData = gtaManager.getPublisherData(courseEnv, gtaNode);
-		subsContext = gtaManager.getSubscriptionContext(courseEnv, gtaNode);
-		if (subsContext != null) {
-			ContextualSubscriptionController contextualSubscriptionCtr = new ContextualSubscriptionController(ureq, getWindowControl(), subsContext, publisherData);
-			listenTo(contextualSubscriptionCtr);
-			mainVC.put("contextualSubscription", contextualSubscriptionCtr.getInitialComponent());
+		if (!markedOnly) {
+			publisherData = gtaManager.getPublisherData(courseEnv, gtaNode);
+			subsContext = gtaManager.getSubscriptionContext(courseEnv, gtaNode);
+			if (subsContext != null) {
+				ContextualSubscriptionController contextualSubscriptionCtr = new ContextualSubscriptionController(ureq, getWindowControl(), subsContext, publisherData);
+				listenTo(contextualSubscriptionCtr);
+				mainVC.put("contextualSubscription", contextualSubscriptionCtr.getInitialComponent());
+			}
 		}
 		
 		ModuleConfiguration config = gtaNode.getModuleConfiguration();
@@ -139,7 +146,7 @@ public class GTACoachSelectionController extends BasicController implements Acti
 				mainVC.put("list", groupListCtrl.getInitialComponent());
 			}	
 		} else {
-			participantListCtrl = new GTACoachedParticipantListController(ureq, getWindowControl(), coachCourseEnv, gtaNode);
+			participantListCtrl = new GTACoachedParticipantListController(ureq, getWindowControl(), coachCourseEnv, gtaNode, markedOnly);
 			listenTo(participantListCtrl);
 			mainVC.put("list", participantListCtrl.getInitialComponent());
 		}
@@ -203,13 +210,17 @@ public class GTACoachSelectionController extends BasicController implements Acti
 	@Override
 	protected void event(UserRequest ureq, Component source, Event event) {
 		if(backLink == source) {
-			back();
+			back(ureq);
 		} else if(downloadButton == source) {
 			doBulkDownload(ureq);
 		}
 	}
 	
-	private void back() {
+	public void reload(UserRequest ureq) {
+		participantListCtrl.updateModel(ureq);
+	}
+	
+	private void back(UserRequest ureq) {
 		if(coachingCtrl != null) {
 			mainVC.remove(coachingCtrl.getInitialComponent());
 			removeAsListenerAndDispose(coachingCtrl);
@@ -217,7 +228,7 @@ public class GTACoachSelectionController extends BasicController implements Acti
 		}
 		backLink.setVisible(false);
 		if (participantListCtrl != null) {
-			participantListCtrl.updateModel();			
+			participantListCtrl.updateModel(ureq);			
 		} 
 		if (groupListCtrl != null) {
 			groupListCtrl.updateModel();
@@ -227,7 +238,7 @@ public class GTACoachSelectionController extends BasicController implements Acti
 	private void doBulkDownload(UserRequest ureq) {
 		if (participantListCtrl != null) {
 			ArchiveOptions asOptions = new ArchiveOptions();
-			asOptions.setIdentities(participantListCtrl.getAssessableIdentities());
+			asOptions.setIdentities(getIdentitesForBulkDownload(ureq));
 			OLATResource ores = courseEnv.getCourseGroupManager().getCourseResource();
 			ArchiveResource resource = new ArchiveResource(gtaNode, ores, asOptions, getLocale());
 			ureq.getDispatchResult().setResultingMediaResource(resource);
@@ -237,6 +248,19 @@ public class GTACoachSelectionController extends BasicController implements Acti
 			ureq.getDispatchResult().setResultingMediaResource(resource);
 		}
 	}
+
+	private List<Identity> getIdentitesForBulkDownload(UserRequest ureq) {
+		List<Identity> identities = participantListCtrl.getAssessableIdentities();
+		if (markedOnly) {
+			RepositoryEntry entry = courseEnv.getCourseGroupManager().getCourseEntry();
+			List<Identity> markedIdentities =
+					gtaManager.getMarks(entry, gtaNode, ureq.getIdentity()).stream()
+							.map(mark -> mark.getParticipant())
+							.collect(Collectors.toList());
+			identities.retainAll(markedIdentities);
+		}
+		return identities;
+	}
 	
 	private Activateable2 doSelectBusinessGroup(UserRequest ureq, BusinessGroup group) {
 		removeAsListenerAndDispose(coachingCtrl);
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedParticipantListController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedParticipantListController.java
index 2f17f92779f..ed3f4b5c444 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedParticipantListController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedParticipantListController.java
@@ -33,10 +33,12 @@ import org.olat.basesecurity.BaseSecurityModule;
 import org.olat.basesecurity.GroupRoles;
 import org.olat.basesecurity.IdentityRef;
 import org.olat.basesecurity.model.IdentityRefImpl;
+import org.olat.core.commons.services.mark.Mark;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItem;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
 import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
 import org.olat.core.gui.components.form.flexible.impl.FormEvent;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiColumnModel;
@@ -45,6 +47,7 @@ import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTable
 import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiCellRenderer;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.TextFlexiCellRenderer;
+import org.olat.core.gui.components.link.Link;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
@@ -56,6 +59,7 @@ import org.olat.core.util.StringHelper;
 import org.olat.course.groupsandrights.CourseGroupManager;
 import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.gta.GTAManager;
+import org.olat.course.nodes.gta.IdentityMark;
 import org.olat.course.nodes.gta.Task;
 import org.olat.course.nodes.gta.TaskLight;
 import org.olat.course.nodes.gta.TaskList;
@@ -92,6 +96,7 @@ public class GTACoachedParticipantListController extends GTACoachedListControlle
 	
 	private final boolean isAdministrativeUser;
 	private final List<UserPropertyHandler> userPropertyHandlers;
+	private final boolean markedOnly;
 
 	private CloseableModalController cmc;
 	private EditDueDatesController editDueDatesCtrl;
@@ -110,7 +115,7 @@ public class GTACoachedParticipantListController extends GTACoachedListControlle
 	private BusinessGroupService businessGroupService;
 	
 	public GTACoachedParticipantListController(UserRequest ureq, WindowControl wControl,
-			UserCourseEnvironment userCourseEnv, GTACourseNode gtaNode) {
+			UserCourseEnvironment userCourseEnv, GTACourseNode gtaNode, boolean markedOnly) {
 		super(ureq, wControl, userCourseEnv.getCourseEnvironment(), gtaNode);
 		
 		Roles roles = ureq.getUserSession().getRoles();
@@ -118,6 +123,7 @@ public class GTACoachedParticipantListController extends GTACoachedListControlle
 		userPropertyHandlers = userManager.getUserPropertyHandlersFor(GTACoachedGroupGradingController.USER_PROPS_ID, isAdministrativeUser);
 		setTranslator(userManager.getPropertyHandlerTranslator(getTranslator()));
 		coachCourseEnv = (UserCourseEnvironmentImpl)userCourseEnv;
+		this.markedOnly = markedOnly;
 
 		assessableIdentities = new ArrayList<>();
 		collectIdentities(new Consumer<Identity>() {
@@ -128,7 +134,7 @@ public class GTACoachedParticipantListController extends GTACoachedListControlle
 		});
 		
 		initForm(ureq);
-		updateModel();
+		updateModel(ureq);
 	}
 	
 	public boolean hasIdentityKey(Long identityKey) {
@@ -185,6 +191,9 @@ public class GTACoachedParticipantListController extends GTACoachedListControlle
 		super.initForm(formLayout, listener, ureq);
 
 		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(CGCols.mark.i18nKey(), CGCols.mark.ordinal(),
+				true, CGCols.mark.name()));
+		
 		if(isAdministrativeUser) {
 			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(CGCols.username.i18nKey(), CGCols.username.ordinal(),
 					true, CGCols.username.name()));
@@ -230,18 +239,32 @@ public class GTACoachedParticipantListController extends GTACoachedListControlle
 		tableEl.setAndLoadPersistedPreferences(ureq, "gta-coached-participants");
 	}
 	
-	protected void updateModel() {
+	protected void updateModel(UserRequest ureq) {
 		RepositoryEntry entry = courseEnv.getCourseGroupManager().getCourseEntry();
 		List<TaskLight> tasks = gtaManager.getTasksLight(entry, gtaNode);
-		Map<Long,TaskLight> identityToTasks = new HashMap<>();
+		Map<Long,TaskLight> identityToTasks = new HashMap<>(tasks.size());
 		for(TaskLight task:tasks) {
 			if(task.getIdentityKey() != null) {
 				identityToTasks.put(task.getIdentityKey(), task);
 			}
 		}
+		List<IdentityMark> marks = gtaManager.getMarks(entry, gtaNode, ureq.getIdentity());
+		Map<Long,IdentityMark> identityToMarks= new HashMap<>(marks.size());
+		for(IdentityMark mark:marks) {
+			if(mark.getParticipant() != null) {
+				identityToMarks.put(mark.getParticipant().getKey(), mark);
+			}
+		}
 		
 		List<CoachedIdentityRow> rows = new ArrayList<>(assessableIdentities.size());
 		for(UserPropertiesRow assessableIdentity:assessableIdentities) {
+			IdentityMark mark = identityToMarks.get(assessableIdentity.getIdentityKey());
+			if (markedOnly && mark == null) continue;
+			
+			FormLink markLink = uifactory.addFormLink("mark_" + assessableIdentity.getIdentityKey(), "mark", "", null, null, Link.NONTRANSLATED);
+			markLink.setIconLeftCSS(mark != null ? Mark.MARK_CSS_LARGE : Mark.MARK_ADD_CSS_LARGE);
+			markLink.setUserObject(assessableIdentity.getIdentityKey());
+			
 			TaskLight task = identityToTasks.get(assessableIdentity.getIdentityKey());
 			Date submissionDueDate = null;
 			if(task == null || task.getTaskStatus() == null || task.getTaskStatus() == TaskProcess.assignment) {
@@ -260,7 +283,8 @@ public class GTACoachedParticipantListController extends GTACoachedListControlle
 					hasSubmittedDocument = hasSubmittedDocument(task);
 				}
 			}
-			rows.add(new CoachedIdentityRow(assessableIdentity, task, submissionDueDate, syntheticSubmissionDate, hasSubmittedDocument));
+			
+			rows.add(new CoachedIdentityRow(assessableIdentity, task, submissionDueDate, syntheticSubmissionDate, hasSubmittedDocument, markLink));
 		}
 		
 		tableModel.setObjects(rows);
@@ -276,7 +300,7 @@ public class GTACoachedParticipantListController extends GTACoachedListControlle
 	public void event(UserRequest ureq, Controller source, Event event) {
 		if(editDueDatesCtrl == source) {
 			if(event == Event.DONE_EVENT) {
-				updateModel();
+				updateModel(ureq);
 			}
 			cmc.deactivate();
 			cleanUp();
@@ -306,6 +330,15 @@ public class GTACoachedParticipantListController extends GTACoachedListControlle
 					fireEvent(ureq, new SelectIdentityEvent(row.getIdentity().getIdentityKey()));	
 				}
 			}
+		} else if(source instanceof FormLink) {
+			FormLink link = (FormLink)source;
+			String cmd = link.getCmd();
+			if("mark".equals(cmd)) {
+				Long assessableIdentityKey = (Long)link.getUserObject();
+				boolean marked = doToogleMark(ureq, assessableIdentityKey);
+				link.setIconLeftCSS(marked ? Mark.MARK_CSS_LARGE : Mark.MARK_ADD_CSS_LARGE);
+				link.getComponent().setDirty(true);
+			}
 		}
 		super.formInnerEvent(ureq, source, event);
 	}
@@ -338,4 +371,11 @@ public class GTACoachedParticipantListController extends GTACoachedListControlle
 		listenTo(cmc);
 		cmc.activate();
 	}
+	
+	private boolean doToogleMark(UserRequest ureq, Long particiantKey) {
+		RepositoryEntry entry = courseEnv.getCourseGroupManager().getCourseEntry();
+		Identity participant = securityManager.loadIdentityByKey(particiantKey);
+		boolean isMarked = gtaManager.toggleMark(entry, gtaNode, ureq.getIdentity(), participant);
+		return isMarked;
+	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTARunController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTARunController.java
index 8b5297d6d3e..5efc2db387c 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTARunController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTARunController.java
@@ -56,9 +56,10 @@ public class GTARunController extends BasicController implements Activateable2 {
 	
 	private GTAParticipantController runCtrl;
 	private GTACoachSelectionController coachCtrl;
+	private GTACoachSelectionController markedCtrl;
 	private GTACoachManagementController manageCtrl;
 
-	private Link runLink, coachLink, manageLink;
+	private Link runLink, coachLink, markedLink, manageLink;
 	private VelocityContainer mainVC;
 	private SegmentViewComponent segmentView;
 	
@@ -84,7 +85,9 @@ public class GTARunController extends BasicController implements Activateable2 {
 			segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this);
 			runLink = LinkFactory.createLink("run.run", mainVC, this);
 			segmentView.addSegment(runLink, false);
-			coachLink = LinkFactory.createLink("run.coach", mainVC, this);
+			markedLink = LinkFactory.createLink("run.coach.marked", mainVC, this);
+			segmentView.addSegment(markedLink, false);
+			coachLink = LinkFactory.createLink("run.coach.all", mainVC, this);
 			segmentView.addSegment(coachLink, true);
 			if(isManagementTabAvalaible(config)) {
 				manageLink = LinkFactory.createLink("run.manage.coach", mainVC, this);
@@ -97,7 +100,9 @@ public class GTARunController extends BasicController implements Activateable2 {
 			mainVC = createVelocityContainer("run_segments");
 
 			segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this);
-			coachLink = LinkFactory.createLink("run.coach", mainVC, this);
+			markedLink = LinkFactory.createLink("run.coach.marked", mainVC, this);
+			segmentView.addSegment(markedLink, false);
+			coachLink = LinkFactory.createLink("run.coach.all", mainVC, this);
 			segmentView.addSegment(coachLink, true);
 			manageLink = LinkFactory.createLink("run.manage.coach", mainVC, this);
 			segmentView.addSegment(manageLink, false);
@@ -106,8 +111,17 @@ public class GTARunController extends BasicController implements Activateable2 {
 			mainVC.put("segments", segmentView);
 			putInitialPanel(mainVC);
 		} else if(membership.isCoach() || userCourseEnv.isAdmin()) {
-			createCoach(ureq);
-			putInitialPanel(coachCtrl.getInitialComponent());
+			mainVC = createVelocityContainer("run_segments");
+
+			segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this);
+			markedLink = LinkFactory.createLink("run.coach.marked", mainVC, this);
+			segmentView.addSegment(markedLink, false);
+			coachLink = LinkFactory.createLink("run.coach.all", mainVC, this);
+			segmentView.addSegment(coachLink, true);
+
+			doOpenCoach(ureq);
+			mainVC.put("segments", segmentView);
+			putInitialPanel(mainVC);
 		} else if(membership.isParticipant()) {
 			createRun(ureq);
 			putInitialPanel(runCtrl.getInitialComponent());
@@ -139,6 +153,13 @@ public class GTARunController extends BasicController implements Activateable2 {
 					segmentView.select(coachLink);
 				}
 			}
+		} else if("marked".equalsIgnoreCase(type)) {
+			if(markedLink != null) {
+				doOpenMarked(ureq);
+				if(segmentView != null) {
+					segmentView.select(markedLink);
+				}
+			}
 		} else if("management".equalsIgnoreCase(type)) {
 			if(manageLink != null) {
 				doManage(ureq);
@@ -168,6 +189,8 @@ public class GTARunController extends BasicController implements Activateable2 {
 					doOpenRun(ureq);
 				} else if (clickedLink == coachLink) {
 					doOpenCoach(ureq);
+				} else if (clickedLink == markedLink) {
+					doOpenMarked(ureq);
 				} else if(clickedLink == manageLink) {
 					doManage(ureq);
 				}
@@ -191,11 +214,25 @@ public class GTARunController extends BasicController implements Activateable2 {
 		}
 		return runCtrl;
 	}
-	
+
+	private Activateable2 doOpenMarked(UserRequest ureq) {
+		if(markedCtrl == null) {
+			createMarked(ureq);
+		} else {
+			markedCtrl.reload(ureq);
+			addToHistory(ureq, markedCtrl);
+		}
+		if(mainVC != null) {
+			mainVC.put("segmentCmp", markedCtrl.getInitialComponent());
+		}
+		return markedCtrl;
+	}	
+
 	private Activateable2 doOpenCoach(UserRequest ureq) {
 		if(coachCtrl == null) {
 			createCoach(ureq);
 		} else {
+			coachCtrl.reload(ureq);
 			addToHistory(ureq, coachCtrl);
 		}
 		if(mainVC != null) {
@@ -223,11 +260,20 @@ public class GTARunController extends BasicController implements Activateable2 {
 		return runCtrl;
 	}
 	
+	private GTACoachSelectionController createMarked(UserRequest ureq) {
+		removeAsListenerAndDispose(markedCtrl);
+		
+		WindowControl swControl = addToHistory(ureq, OresHelper.createOLATResourceableType("marked"), null);
+		markedCtrl = new GTACoachSelectionController(ureq, swControl, userCourseEnv, gtaNode, true);
+		listenTo(markedCtrl);
+		return coachCtrl;
+	}
+	
 	private GTACoachSelectionController createCoach(UserRequest ureq) {
 		removeAsListenerAndDispose(coachCtrl);
 		
 		WindowControl swControl = addToHistory(ureq, OresHelper.createOLATResourceableType("coach"), null);
-		coachCtrl = new GTACoachSelectionController(ureq, swControl, userCourseEnv, gtaNode);
+		coachCtrl = new GTACoachSelectionController(ureq, swControl, userCourseEnv, gtaNode, false);
 		listenTo(coachCtrl);
 		return coachCtrl;
 	}
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/coach_selection.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/coach_selection.html
index 391b4852ad5..b1b042b01dc 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_content/coach_selection.html
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/coach_selection.html
@@ -16,10 +16,10 @@
 	
 	$r.render("selection")
 #elseif($r.available("list"))
-	#if($r.available("contextualSubscription"))
-		<div class="o_button_group o_button_group_right">
-			$r.render("bulk.download.title")
+	<div class="o_button_group o_button_group_right">
+		$r.render("bulk.download.title")
 			
+		#if($r.available("contextualSubscription"))
 			<div style="display:inline-block;">
 			$r.render("contextualSubscription")
 			</div>
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties
index 4395fde0308..ee9f6517384 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties
@@ -205,8 +205,9 @@ revision.period=\u00DCberarbeitung
 revisions.duedate=\u00DCberarbeitung bis...
 run.assignment.due.date=Zuweisung Termin\: {0}
 run.assignment.title=Zuweisung Aufgabenstellung
-run.coach=Korrigieren
+run.coach.all=Alle Teilnehmer
 run.coach.corrections.description=Sie haben die folgende Korrekturanforderung erstellt.
+run.coach.marked=Favoriten
 run.corrections.description=Ihr Betreuer hat folgende Dokumente f\u00FCr Sie hinzugef\u00FCgt\:
 run.corrections.rejected=$\:coach.corrections.rejected
 run.documents.successfully.submitted=Ihr(e) Dokument(e) wurden erfolgreich eingereicht.
@@ -279,6 +280,7 @@ table.header.edit=Aktion
 table.header.group.name=Gruppe
 table.header.group.step=Schritt
 table.header.group.taskName=Aufgabe
+table.header.mark=<i class\="o_icon o_icon_bookmark_header o_icon-lg" title\="Favorit"> </i>
 table.header.passed=Bestanden
 table.header.score=Punkte
 table.header.submissionDate=Abgabedatum
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties
index 436e44a116c..30c85a6ff3f 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties
@@ -205,8 +205,9 @@ revision.period=Revision
 revisions.duedate=Revision until...
 run.assignment.due.date=Deadline\: {0}
 run.assignment.title=Task assignment
-run.coach=Submissions
+run.coach.all=All participants
 run.coach.corrections.description=You have set the following correction request.
+run.coach.marked=Favorites
 run.corrections.description=Your coach attached the following files for you\:
 run.corrections.rejected=Your submission has been rejected.
 run.documents.successfully.submitted=Your document(s) have been successfully submitted.
@@ -279,6 +280,7 @@ table.header.edit=Action
 table.header.group.name=Group
 table.header.group.step=Step
 table.header.group.taskName=Task
+table.header.mark=<i class\="o_icon o_icon_bookmark_header o_icon-lg" title\="Bookmark"> </i>
 table.header.passed=Passed
 table.header.score=Points
 table.header.submissionDate=Submission date
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_fr.properties
index 9f24e8fe147..66335c2ea68 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_fr.properties
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_fr.properties
@@ -205,7 +205,6 @@ revision.period=P\u00E9riode de r\u00E9\u00E9criture
 revisions.duedate=Date d'\u00E9ch\u00E9ance de r\u00E9vision
 run.assignment.due.date=D\u00E9lai pour l'affectation\: {0}
 run.assignment.title=Affectation d'un devoir
-run.coach=Corriger
 run.coach.corrections.description=Vous avez retourner les demandes de corrections suivantes.
 run.corrections.description=Votre coach a ajout\u00E9 les documents suivants \u00E0 votre intention\:
 run.corrections.rejected=$\:coach.corrections.rejected
@@ -279,6 +278,7 @@ table.header.edit=Action
 table.header.group.name=Groupe
 table.header.group.step=Etape
 table.header.group.taskName=Devoir
+table.header.mark=<i class\="o_icon o_icon_bookmark_header o_icon-lg" title\="Favori"> </i>
 table.header.passed=R\u00E9ussi
 table.header.score=Points
 table.header.submissionDate=Date de remise
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_it.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_it.properties
index fb6f1a00a44..88baefa419a 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_it.properties
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_it.properties
@@ -163,7 +163,6 @@ revision.enabled=Abilita la cartella per le revisioni dei partecipanti, pu\u00F2
 revision.period=Periodo di revisione
 run.assignment.due.date=Data di scadenza\: {0}
 run.assignment.title=Assegnazione del compito
-run.coach=Correzione
 run.coach.corrections.description=Hai impostato le seguenti richieste di correzione.
 run.corrections.description=Il tutore ha allegato i seguenti file per te\:
 run.corrections.rejected=$\:coach.corrections.rejected
@@ -232,6 +231,7 @@ table.header.edit=Azione
 table.header.group.name=Gruppo
 table.header.group.step=Passo
 table.header.group.taskName=Compito
+table.header.mark=<i class\="o_icon o_icon_bookmark_header o_icon-lg" title\="Preferito"> </i>
 table.header.passed=Superato
 table.header.score=Punti
 table.header.uploaded.by=Caricato da
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pl.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pl.properties
index 3be48f06b75..9f09c19f1bf 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pl.properties
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pl.properties
@@ -157,7 +157,6 @@ revision.enabled=Enable drop box for revisions by participants, can be set by co
 revision.period=Revision phase
 run.assignment.due.date=Due date\: {0}
 run.assignment.title=Przypisanie zada\u0144
-run.coach=Correct
 run.coach.corrections.description=You have set the following correction request.
 run.corrections.description=Your coach attached the following files for you\:
 run.corrections.rejected=$\:coach.corrections.rejected
@@ -226,6 +225,7 @@ table.header.edit=Akcja
 table.header.group.name=Grupa
 table.header.group.step=Krok
 table.header.group.taskName=Zadanie
+table.header.mark=<i class\="o_icon o_icon_bookmark_header o_icon-lg" title\="Ulubione"> </i>
 table.header.passed=Passed
 table.header.score=Punkty
 table.header.uploaded.by=Przes\u0142ane przez
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pt_BR.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pt_BR.properties
index c4bc05d538b..0bc2cfb2ef1 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pt_BR.properties
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pt_BR.properties
@@ -205,7 +205,6 @@ revision.period=Fase de revis\u00E3o
 revisions.duedate=Revis\u00E3o at\u00E9...
 run.assignment.due.date=Data de vencimento\: {0}
 run.assignment.title=Atribui\u00E7\u00E3o de tarefas
-run.coach=Correto
 run.coach.corrections.description=Voc\u00EA definiu a seguinte solicita\u00E7\u00E3o de corre\u00E7\u00E3o.
 run.corrections.description=Seu treinador anexou os seguintes arquivos para voc\u00EA\:
 run.corrections.rejected=$\:coach.corrections.rejected
@@ -279,6 +278,7 @@ table.header.edit=A\u00E7\u00E3o
 table.header.group.name=Grupo
 table.header.group.step=Passo
 table.header.group.taskName=Tarefa
+table.header.mark=<i class\="o_icon o_icon_bookmark_header o_icon-lg" title\="Favorito"> </i>s
 table.header.passed=Passou
 table.header.score=Pontos
 table.header.submissionDate=Data para envio
diff --git a/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml b/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
index 062ee483753..d1437dceb8b 100644
--- a/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
+++ b/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
@@ -160,6 +160,10 @@
 					<constructor-arg index="0" value="OLAT_12.1.1" />
 					<property name="alterDbStatements" value="alter_12_1_0_to_12_1_1.sql" />
 				</bean>
+				<bean id="database_upgrade_12_1_x" class="org.olat.upgrade.DatabaseUpgrade">
+					<constructor-arg index="0" value="OLAT_12.2.0" />
+					<property name="alterDbStatements" value="alter_12_1_x_to_12_2_0.sql" />
+				</bean>
 			</list>
 		</property>
 	</bean>
diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml
index b82a337e9e4..c2bb7561ccb 100644
--- a/src/main/resources/META-INF/persistence.xml
+++ b/src/main/resources/META-INF/persistence.xml
@@ -91,6 +91,7 @@
 		<class>org.olat.course.assessment.model.UserEfficiencyStatementRepoImpl</class>
 		<class>org.olat.course.nodes.cl.model.DBCheckbox</class>
 		<class>org.olat.course.nodes.cl.model.DBCheck</class>
+		<class>org.olat.course.nodes.gta.model.IdentityMarkImpl</class>
 		<class>org.olat.course.nodes.gta.model.TaskImpl</class>
 		<class>org.olat.course.nodes.gta.model.TaskLightImpl</class>
 		<class>org.olat.course.nodes.gta.model.TaskDueDateImpl</class>
diff --git a/src/main/resources/database/mysql/alter_12_1_x_to_12_2_0.sql b/src/main/resources/database/mysql/alter_12_1_x_to_12_2_0.sql
new file mode 100644
index 00000000000..10781511f6c
--- /dev/null
+++ b/src/main/resources/database/mysql/alter_12_1_x_to_12_2_0.sql
@@ -0,0 +1,14 @@
+create table o_gta_mark (
+  id bigint not null auto_increment,
+  creationdate datetime not null,
+  lastmodified datetime not null,
+  fk_tasklist_id int8 not null,
+  fk_marker_identity_id int8 not null,
+  fk_participant_identity_id int8 not null,
+  primary key (id)
+);
+
+alter table o_gta_mark ENGINE = InnoDB;
+
+alter table o_gta_mark add constraint gtamark_tasklist_idx foreign key (fk_tasklist) references o_gta_task_list (id);
+create index idx_gtamark_tasklist_idx on o_gta_task (fk_tasklist);
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index 5ee96a2de5e..07fde0ee7e2 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -1905,6 +1905,16 @@ create table o_gta_task_revision_date (
   primary key (id)
 );
 
+create table o_gta_mark (
+  id bigint not null auto_increment,
+  creationdate datetime not null,
+  lastmodified datetime not null,
+  fk_tasklist_id int8 not null,
+  fk_marker_identity_id int8 not null,
+  fk_participant_identity_id int8 not null,
+  primary key (id)
+);
+
 create table o_rem_reminder (
    id bigint not null,
    creationdate datetime not null,
@@ -2439,6 +2449,7 @@ alter table o_cl_check ENGINE = InnoDB;
 alter table o_gta_task_list ENGINE = InnoDB;
 alter table o_gta_task ENGINE = InnoDB;
 alter table o_gta_task_revision_date ENGINE = InnoDB;
+alter table o_gta_mark ENGINE = InnoDB;
 alter table o_cer_template ENGINE = InnoDB;
 alter table o_cer_certificate ENGINE = InnoDB;
 alter table o_rem_reminder ENGINE = InnoDB;
@@ -2669,6 +2680,8 @@ alter table o_gta_task_list add constraint gta_list_to_repo_entry_idx foreign ke
 
 alter table o_gta_task_revision_date add constraint gtaskrev_to_task_idx foreign key (fk_task) references o_gta_task (id);
 
+alter table o_gta_mark add constraint gtamark_tasklist_idx foreign key (fk_tasklist) references o_gta_task_list (id);
+
 -- reminders
 alter table o_rem_reminder add constraint rem_reminder_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id);
 alter table o_rem_reminder add constraint rem_reminder_to_creator_idx foreign key (fk_creator) references o_bs_identity (id);
diff --git a/src/main/resources/database/oracle/alter_12_1_x_to_12_2_0.sql b/src/main/resources/database/oracle/alter_12_1_x_to_12_2_0.sql
new file mode 100644
index 00000000000..7c26181ffff
--- /dev/null
+++ b/src/main/resources/database/oracle/alter_12_1_x_to_12_2_0.sql
@@ -0,0 +1,12 @@
+create table o_gta_mark (
+  id number(20) generated always as identity,
+  creationdate date not null,
+  lastmodified date not null,
+  fk_tasklist_id number(20) not null,
+  fk_marker_identity_id number(20) not null,
+  fk_participant_identity_id number(20) not null,
+  primary key (id)
+);
+
+alter table o_gta_mark add constraint gtamark_tasklist_idx foreign key (fk_tasklist) references o_gta_task_list (id);
+create index idx_gtamark_tasklist_idx on o_gta_task (fk_tasklist);
diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql
index eb454ee8398..681ec404d17 100644
--- a/src/main/resources/database/oracle/setupDatabase.sql
+++ b/src/main/resources/database/oracle/setupDatabase.sql
@@ -1928,6 +1928,16 @@ create table o_gta_task_revision_date (
   primary key (id)
 );
 
+create table o_gta_mark (
+  id number(20) generated always as identity,
+  creationdate date not null,
+  lastmodified date not null,
+  fk_tasklist_id number(20) not null,
+  fk_marker_identity_id number(20) not null,
+  fk_participant_identity_id number(20) not null,
+  primary key (id)
+);
+
 create table o_rem_reminder (
    id number(20) not null,
    creationdate date not null,
@@ -2740,6 +2750,9 @@ create index idx_gta_list_to_repo_entry_idx on o_gta_task_list (fk_entry);
 alter table o_gta_task_revision_date add constraint gtaskrev_to_task_idx foreign key (fk_task) references o_gta_task (id);
 create index idx_gtaskrev_to_task_idx on o_gta_task_revision_date (fk_task);
 
+alter table o_gta_mark add constraint gtamark_tasklist_idx foreign key (fk_tasklist) references o_gta_task_list (id);
+create index idx_gtamark_tasklist_idx on o_gta_task (fk_tasklist);
+
 -- reminders
 alter table o_rem_reminder add constraint rem_reminder_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id);
 create index idx_reminder_to_repo_entry_idx on o_rem_reminder (fk_entry);
diff --git a/src/main/resources/database/postgresql/alter_12_1_x_to_12_2_0.sql b/src/main/resources/database/postgresql/alter_12_1_x_to_12_2_0.sql
new file mode 100644
index 00000000000..1576e8461a6
--- /dev/null
+++ b/src/main/resources/database/postgresql/alter_12_1_x_to_12_2_0.sql
@@ -0,0 +1,12 @@
+create table o_gta_mark (
+  id bigserial not null,
+  creationdate timestamp not null,
+  lastmodified timestamp not null,
+  fk_tasklist_id int8 not null,
+  fk_marker_identity_id int8 not null,
+  fk_participant_identity_id int8 not null,
+  primary key (id)
+);
+
+alter table o_gta_mark add constraint gtamark_tasklist_idx foreign key (fk_tasklist) references o_gta_task_list (id);
+create index idx_gtamark_tasklist_idx on o_gta_task (fk_tasklist);
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index 8c833977caf..0c4643019f1 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -1902,6 +1902,16 @@ create table o_gta_task_revision_date (
   primary key (id)
 );
 
+create table o_gta_mark (
+  id bigserial not null,
+  creationdate timestamp not null,
+  lastmodified timestamp not null,
+  fk_tasklist_id int8 not null,
+  fk_marker_identity_id int8 not null,
+  fk_participant_identity_id int8 not null,
+  primary key (id)
+);
+
 create table o_rem_reminder (
    id int8 not null,
    creationdate timestamp not null,
@@ -2589,6 +2599,9 @@ create index idx_gta_list_to_repo_entry_idx on o_gta_task_list (fk_entry);
 alter table o_gta_task_revision_date add constraint gtaskrev_to_task_idx foreign key (fk_task) references o_gta_task (id);
 create index idx_gtaskrev_to_task_idx on o_gta_task_revision_date (fk_task);
 
+alter table o_gta_mark add constraint gtamark_tasklist_idx foreign key (fk_tasklist) references o_gta_task_list (id);
+create index idx_gtamark_tasklist_idx on o_gta_task (fk_tasklist)
+
 -- reminders
 alter table o_rem_reminder add constraint rem_reminder_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id);
 create index idx_reminder_to_repo_entry_idx on o_rem_reminder (fk_entry);
diff --git a/src/test/java/org/olat/course/nodes/gta/manager/GTAIdentityMarkDAOTest.java b/src/test/java/org/olat/course/nodes/gta/manager/GTAIdentityMarkDAOTest.java
new file mode 100644
index 00000000000..e30953d7e15
--- /dev/null
+++ b/src/test/java/org/olat/course/nodes/gta/manager/GTAIdentityMarkDAOTest.java
@@ -0,0 +1,190 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.nodes.gta.manager;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.Identity;
+import org.olat.course.nodes.gta.IdentityMark;
+import org.olat.course.nodes.gta.TaskList;
+import org.olat.course.nodes.gta.model.TaskListImpl;
+import org.olat.test.JunitTestHelper;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 03.10.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class GTAIdentityMarkDAOTest extends OlatTestCase {
+
+	@Autowired
+	private DB dbInstance;
+	
+	@Autowired
+	private GTAIdentityMarkDAO sut;
+	
+	@Before
+	public void emptyTable() {
+		String statement = "delete from gtaMark";
+		dbInstance.getCurrentEntityManager().createQuery(statement).executeUpdate();
+	}
+
+	
+	@Test
+	public void shouldCreateAndPersistMark() {
+		TaskList taskList = createTaskList();
+		Identity marker = JunitTestHelper.createAndPersistIdentityAsAuthor("coach");
+		Identity participant = JunitTestHelper.createAndPersistIdentityAsUser("participant");
+		dbInstance.commitAndCloseSession();
+		
+		sut.createAndPersisitMark(taskList, marker, participant);
+		dbInstance.commitAndCloseSession();
+		
+		IdentityMark reloadedMark = sut.loadMarks(taskList, marker).get(0);
+		assertThat(reloadedMark).isNotNull();
+		assertThat(reloadedMark.getCreationDate()).isNotNull();
+		assertThat(reloadedMark.getLastModified()).isNotNull();
+		assertThat(reloadedMark.getTaskList()).isEqualTo(taskList);
+		assertThat(reloadedMark.getMarker()).isEqualTo(marker);
+		assertThat(reloadedMark.getParticipant()).isEqualTo(participant);
+	}
+	
+	@Test
+	public void shouldLoadAllMarksOfAMarker() {
+		TaskList taskList = createTaskList();
+		TaskList otherTaskList = createTaskList();
+		Identity marker = JunitTestHelper.createAndPersistIdentityAsAuthor("coach");
+		Identity otherMarker = JunitTestHelper.createAndPersistIdentityAsAuthor("otherCoach");
+		Identity participant1 = JunitTestHelper.createAndPersistIdentityAsUser("participant1");
+		Identity participant2 = JunitTestHelper.createAndPersistIdentityAsUser("participant2");
+		Identity participant3 = JunitTestHelper.createAndPersistIdentityAsUser("participant3");
+		IdentityMark mark1 = sut.createAndPersisitMark(taskList, marker, participant1);
+		IdentityMark mark2 = sut.createAndPersisitMark(taskList, marker, participant2);
+		sut.createAndPersisitMark(otherTaskList, marker, participant3);
+		sut.createAndPersisitMark(taskList, otherMarker, participant3);
+		IdentityMark mark3 = sut.createAndPersisitMark(taskList, marker, participant3);
+		dbInstance.commitAndCloseSession();
+		
+		List<IdentityMark> marks = sut.loadMarks(taskList, marker);
+		
+		assertThat(marks).hasSize(3);
+		assertThat(marks).containsExactly(mark1, mark2, mark3);
+	}
+	
+	@Test
+	public void shouldCheckIfMarked() {
+		TaskList taskList = createTaskList();
+		Identity marker = JunitTestHelper.createAndPersistIdentityAsAuthor("coach");
+		Identity participant = JunitTestHelper.createAndPersistIdentityAsUser("participant");
+		sut.createAndPersisitMark(taskList, marker, participant);
+		dbInstance.commitAndCloseSession();
+		
+		boolean isMarked = sut.isMarked(taskList, marker, participant);
+		
+		assertThat(isMarked).isTrue();
+	}
+	
+	@Test
+	public void shouldCheckIfNotMarked() {
+		TaskList taskList = createTaskList();
+		Identity marker = JunitTestHelper.createAndPersistIdentityAsAuthor("coach");
+		Identity participant = JunitTestHelper.createAndPersistIdentityAsUser("participant");
+		sut.createAndPersisitMark(taskList, marker, participant);
+		Identity participantNotMarked = JunitTestHelper.createAndPersistIdentityAsUser("participantNotMarked");
+		dbInstance.commitAndCloseSession();
+		
+		boolean isMarked = sut.isMarked(taskList, marker, participantNotMarked);
+		
+		assertThat(isMarked).isFalse();
+	}
+	
+	@Test
+	public void shouldDeleteMarkOfAMarker() {
+		TaskList taskList = createTaskList();
+		TaskList otherTaskList = createTaskList();
+		Identity marker = JunitTestHelper.createAndPersistIdentityAsAuthor("coach");
+		Identity otherMarker = JunitTestHelper.createAndPersistIdentityAsAuthor("otherCoach");
+		Identity participant1 = JunitTestHelper.createAndPersistIdentityAsUser("participant1");
+		Identity participant2 = JunitTestHelper.createAndPersistIdentityAsUser("participant2");
+		Identity participant3 = JunitTestHelper.createAndPersistIdentityAsUser("participant3");
+		IdentityMark mark1 = sut.createAndPersisitMark(taskList, marker, participant1);
+		sut.createAndPersisitMark(taskList, marker, participant2);
+		sut.createAndPersisitMark(otherTaskList, marker, participant3);
+		sut.createAndPersisitMark(taskList, otherMarker, participant3);
+		IdentityMark mark3 = sut.createAndPersisitMark(taskList, marker, participant3);
+		dbInstance.commitAndCloseSession();
+		
+		sut.deleteMark(taskList, marker, participant2);
+		dbInstance.commitAndCloseSession();
+		
+		List<IdentityMark> marks = sut.loadMarks(taskList, marker);
+		assertThat(marks).hasSize(2);
+		assertThat(marks).containsExactly(mark1, mark3);
+	}
+	
+	@Test
+	public void shouldDeleteMarkOfATaskList() {
+		TaskList taskList = createTaskList();
+		TaskList otherTaskList = createTaskList();
+		Identity marker = JunitTestHelper.createAndPersistIdentityAsAuthor("coach");
+		Identity otherMarker = JunitTestHelper.createAndPersistIdentityAsAuthor("otherCoach");
+		Identity participant1 = JunitTestHelper.createAndPersistIdentityAsUser("participant1");
+		Identity participant2 = JunitTestHelper.createAndPersistIdentityAsUser("participant2");
+		Identity participant3 = JunitTestHelper.createAndPersistIdentityAsUser("participant3");
+		sut.createAndPersisitMark(taskList, marker, participant1);
+		sut.createAndPersisitMark(taskList, marker, participant2);
+		sut.createAndPersisitMark(otherTaskList, marker, participant3);
+		sut.createAndPersisitMark(taskList, otherMarker, participant3);
+		sut.createAndPersisitMark(taskList, marker, participant3);
+		dbInstance.commitAndCloseSession();
+		
+		int numberDeleted = sut.deleteMark(taskList);
+		dbInstance.commitAndCloseSession();
+		
+		assertThat(numberDeleted).isSameAs(4);
+		List<IdentityMark> marksOfDeletedTaskList = sut.loadMarks(taskList, marker);
+		assertThat(marksOfDeletedTaskList).hasSize(0);
+		List<IdentityMark> marksOfExistinTaskList = sut.loadMarks(otherTaskList, marker);
+		assertThat(marksOfExistinTaskList).hasSize(1);
+	}
+	
+
+	private TaskList createTaskList() {
+		TaskListImpl tasksImpl = new TaskListImpl();
+		Date creationDate = new Date();
+		tasksImpl.setCreationDate(creationDate);
+		tasksImpl.setLastModified(creationDate);
+		tasksImpl.setEntry(JunitTestHelper.createAndPersistRepositoryEntry());
+		tasksImpl.setCourseNodeIdent(UUID.randomUUID().toString());
+		dbInstance.getCurrentEntityManager().persist(tasksImpl);
+		return tasksImpl;
+	}
+	
+}
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index 787bc6b3cfa..3f477fd5ef1 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -137,6 +137,7 @@ import org.junit.runners.Suite;
 	org.olat.course.nodes.en.EnrollmentManagerSerialTest.class,
 	org.olat.course.nodes.en.EnrollmentManagerConcurrentTest.class,
 	org.olat.course.nodes.gta.manager.GTAManagerTest.class,
+	org.olat.course.nodes.gta.manager.GTAIdentityMarkDAOTest.class,
 	org.olat.course.nodes.gta.rule.GTAReminderRuleTest.class,
 	org.olat.course.nodes.pf.manager.PFManagerTest.class,
 	org.olat.course.assessment.AssessmentManagerTest.class,
-- 
GitLab