From 2d361cfb9fe0d1bf7ea89d461fb8b4b160afbd0d Mon Sep 17 00:00:00 2001
From: uhensler <urs.hensler@frentix.com>
Date: Wed, 8 Jan 2020 15:40:36 +0100
Subject: [PATCH] OO-4358: Store live stream usage is separate table

---
 .../course/nodes/LiveStreamCourseNode.java    |   4 +-
 .../olat/course/nodes/livestream/Launch.java  |  44 +++++
 .../nodes/livestream/LiveStreamService.java   |  20 ++-
 ...isticDAO.java => LiveStreamLaunchDAO.java} |  39 +++--
 .../manager/LiveStreamServiceImpl.java        |  14 +-
 .../nodes/livestream/model/LaunchImpl.java    | 160 ++++++++++++++++++
 .../ui/LiveStreamRunController.java           |  19 ++-
 .../ui/LiveStreamStatisticController.java     |  10 +-
 .../org/olat/upgrade/OLATUpgrade_14_2_0.java  | 108 ++++++++++++
 src/main/resources/META-INF/persistence.xml   |   1 +
 .../database/mysql/alter_14_1_x_to_14_2_0.sql |  13 ++
 .../database/mysql/setupDatabase.sql          |  16 ++
 .../oracle/alter_14_1_x_to_14_2_0.sql         |  12 ++
 .../database/oracle/setupDatabase.sql         |  15 ++
 .../postgresql/alter_14_1_x_to_14_2_0.sql     |  11 +-
 .../database/postgresql/setupDatabase.sql     |  14 +-
 .../manager/LiveStreamLaunchDAOTest.java      | 100 +++++++++++
 .../manager/LiveStreamStatisticDAOTest.java   |  89 ----------
 .../java/org/olat/test/AllTestsJunit4.java    |   2 +-
 19 files changed, 568 insertions(+), 123 deletions(-)
 create mode 100644 src/main/java/org/olat/course/nodes/livestream/Launch.java
 rename src/main/java/org/olat/course/nodes/livestream/manager/{LiveStreamStatisticDAO.java => LiveStreamLaunchDAO.java} (56%)
 create mode 100644 src/main/java/org/olat/course/nodes/livestream/model/LaunchImpl.java
 create mode 100644 src/test/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAOTest.java
 delete mode 100644 src/test/java/org/olat/course/nodes/livestream/manager/LiveStreamStatisticDAOTest.java

diff --git a/src/main/java/org/olat/course/nodes/LiveStreamCourseNode.java b/src/main/java/org/olat/course/nodes/LiveStreamCourseNode.java
index d425a2da081..b8418607fbf 100644
--- a/src/main/java/org/olat/course/nodes/LiveStreamCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/LiveStreamCourseNode.java
@@ -47,7 +47,6 @@ import org.olat.course.run.userview.NodeEvaluation;
 import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.repository.RepositoryEntry;
-import org.olat.resource.OLATResource;
 
 /**
  * 
@@ -96,8 +95,7 @@ public class LiveStreamCourseNode extends AbstractAccessableCourseNode {
 			CourseCalendars calendars = CourseCalendars.createCourseCalendarsWrapper(ureq, wControl, userCourseEnv, ne);
 			LiveStreamSecurityCallback secCallback = LiveStreamSecurityCallbackFactory
 					.createSecurityCallback(userCourseEnv, this.getModuleConfiguration());
-			OLATResource courseOres = userCourseEnv.getCourseEnvironment().getCourseGroupManager().getCourseResource();
-			runCtrl = new LiveStreamRunController(ureq, wControl, this, courseOres, secCallback, calendars);
+			runCtrl = new LiveStreamRunController(ureq, wControl, this, userCourseEnv, secCallback, calendars);
 		}
 		Controller ctrl = TitledWrapperHelper.getWrapper(ureq, wControl, runCtrl, this, "o_livestream_icon");
 		return new NodeRunConstructionResult(ctrl);
diff --git a/src/main/java/org/olat/course/nodes/livestream/Launch.java b/src/main/java/org/olat/course/nodes/livestream/Launch.java
new file mode 100644
index 00000000000..25cb61e35dc
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/livestream/Launch.java
@@ -0,0 +1,44 @@
+/**
+ * <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.livestream;
+
+import java.util.Date;
+
+import org.olat.core.id.CreateInfo;
+import org.olat.core.id.Identity;
+import org.olat.repository.RepositoryEntry;
+
+/**
+ * 
+ * Initial date: 8 Jan 2020<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public interface Launch extends CreateInfo {
+	
+	Date getLaunchDate();
+	
+	RepositoryEntry getCourseEntry();
+	
+	String getSubIdent();
+	
+	Identity getIdentity();
+
+}
diff --git a/src/main/java/org/olat/course/nodes/livestream/LiveStreamService.java b/src/main/java/org/olat/course/nodes/livestream/LiveStreamService.java
index 6a534b4ea1b..e9d16eea850 100644
--- a/src/main/java/org/olat/course/nodes/livestream/LiveStreamService.java
+++ b/src/main/java/org/olat/course/nodes/livestream/LiveStreamService.java
@@ -23,7 +23,10 @@ import java.util.Date;
 import java.util.List;
 import java.util.concurrent.ScheduledExecutorService;
 
+import org.olat.core.id.Identity;
 import org.olat.course.nodes.cal.CourseCalendars;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryEntryRef;
 
 /**
  * 
@@ -43,14 +46,23 @@ public interface LiveStreamService {
 	List<? extends LiveStreamEvent> getUpcomingEvents(CourseCalendars calendars, int bufferBeforeMin);
 	
 	/**
-	 * Get the number of unique viewers of the live stream event.
+	 * Create a new launch of a course node by the identity.
+	 *
+	 * @param courseEntry
+	 * @param subIdent
+	 * @param identity
+	 */
+	void createLaunch(RepositoryEntry courseEntry, String subIdent, Identity identity);
+
+	/**
+	 * Get the number of unique launchers (viewers) of the live stream event.
 	 * 
-	 * @param courseResId
-	 * @param courseNodeIdent
+	 * @param courseEntry
+	 * @param subIdent
 	 * @param from
 	 * @param to
 	 * @return
 	 */
-	Long getViewers(String courseResId, String courseNodeIdent, Date from, Date to);
+	Long getLaunchers(RepositoryEntryRef courseEntry, String subIdent, Date from, Date to);
 
 }
diff --git a/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamStatisticDAO.java b/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAO.java
similarity index 56%
rename from src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamStatisticDAO.java
rename to src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAO.java
index 52d933eb2f7..6c350245647 100644
--- a/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamStatisticDAO.java
+++ b/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAO.java
@@ -24,6 +24,11 @@ import java.util.List;
 
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.commons.persistence.QueryBuilder;
+import org.olat.core.id.Identity;
+import org.olat.course.nodes.livestream.Launch;
+import org.olat.course.nodes.livestream.model.LaunchImpl;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryEntryRef;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -34,29 +39,39 @@ import org.springframework.stereotype.Component;
  *
  */
 @Component
-public class LiveStreamStatisticDAO {
+public class LiveStreamLaunchDAO {
 
 	@Autowired
 	private DB dbInstance;
 	
-	public Long getViewers(String courseResId, String nodeIdent, Date from, Date to) {
+	public Launch create(RepositoryEntry courseEntry, String subIdent, Identity identity, Date launchDate) {
+		LaunchImpl launchImpl = new LaunchImpl();
+		launchImpl.setCreationDate(new Date());
+		launchImpl.setLaunchDate(launchDate);
+		launchImpl.setCourseEntry(courseEntry);
+		launchImpl.setSubIdent(subIdent);
+		launchImpl.setIdentity(identity);
+		dbInstance.getCurrentEntityManager().persist(launchImpl);
+		return launchImpl;
+	}
+
+	public Long getLaunchers(RepositoryEntryRef courseEntry, String subIdent, Date from, Date to) {
 		QueryBuilder sb = new QueryBuilder();
-		sb.append("select count(distinct log.userId)");
-		sb.append("  from loggingobject log");
-		sb.and().append("log.actionVerb = 'launch'");
-		sb.and().append("log.targetResType = 'livestream'");
-		sb.and().append("log.targetResId = :targetResId");
-		sb.and().append("log.parentResId = :parentResId");
-		sb.and().append("log.creationDate >= :from");
-		sb.and().append("log.creationDate <= :to");
+		sb.append("select count(distinct launch.identity.key)");
+		sb.append("  from livestreamlaunch launch");
+		sb.and().append("launch.courseEntry.key = :courseEntryKey");
+		sb.and().append("launch.subIdent = :subIdent");
+		sb.and().append("launch.launchDate >= :from");
+		sb.and().append("launch.launchDate <= :to");
 		
 		List<Long> counts = dbInstance.getCurrentEntityManager()
 				.createQuery(sb.toString(), Long.class)
-				.setParameter("targetResId", nodeIdent)
-				.setParameter("parentResId", courseResId)
+				.setParameter("courseEntryKey", courseEntry.getKey())
+				.setParameter("subIdent", subIdent)
 				.setParameter("from", from)
 				.setParameter("to", to)
 				.getResultList();
 		return !counts.isEmpty()? counts.get(0): null;
 	}
+
 }
diff --git a/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java b/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java
index 6dde2be5ca4..c4826a717d7 100644
--- a/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java
+++ b/src/main/java/org/olat/course/nodes/livestream/manager/LiveStreamServiceImpl.java
@@ -34,10 +34,13 @@ import org.olat.commons.calendar.CalendarManager;
 import org.olat.commons.calendar.CalendarUtils;
 import org.olat.commons.calendar.model.KalendarEvent;
 import org.olat.commons.calendar.ui.components.KalendarRenderWrapper;
+import org.olat.core.id.Identity;
 import org.olat.course.nodes.cal.CourseCalendars;
 import org.olat.course.nodes.livestream.LiveStreamEvent;
 import org.olat.course.nodes.livestream.LiveStreamService;
 import org.olat.course.nodes.livestream.model.LiveStreamEventImpl;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryEntryRef;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
 import org.springframework.stereotype.Service;
@@ -56,7 +59,7 @@ public class LiveStreamServiceImpl implements LiveStreamService {
 	@Autowired
 	private CalendarManager calendarManager;
 	@Autowired
-	private LiveStreamStatisticDAO statisticDao;
+	private LiveStreamLaunchDAO launchDao;
 
 	@Override
 	public ScheduledExecutorService getScheduler() {
@@ -166,7 +169,12 @@ public class LiveStreamServiceImpl implements LiveStreamService {
 	}
 
 	@Override
-	public Long getViewers(String courseResId, String nodeIdent, Date from, Date to) {
-		return statisticDao.getViewers(courseResId, nodeIdent, from, to);
+	public void createLaunch(RepositoryEntry courseEntry, String subIdent, Identity identity) {
+		launchDao.create(courseEntry, subIdent, identity, new Date());
+	}
+
+	@Override
+	public Long getLaunchers(RepositoryEntryRef courseEntry, String subIdent, Date from, Date to) {
+		return launchDao.getLaunchers(courseEntry, subIdent, from, to);
 	}
 }
diff --git a/src/main/java/org/olat/course/nodes/livestream/model/LaunchImpl.java b/src/main/java/org/olat/course/nodes/livestream/model/LaunchImpl.java
new file mode 100644
index 00000000000..1843ddd974f
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/livestream/model/LaunchImpl.java
@@ -0,0 +1,160 @@
+/**
+ * <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.livestream.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.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.livestream.Launch;
+import org.olat.repository.RepositoryEntry;
+
+/**
+ * 
+ * Initial date: 8 Jan 2020<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="livestreamlaunch")
+@Table(name="o_livestream_launch")
+public class LaunchImpl implements Launch, Persistable {
+
+	private static final long serialVersionUID = -3006434011572074780L;
+
+	@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="l_launch_date", nullable=false, insertable=true, updatable=false)
+	private Date launchDate;
+	
+	@ManyToOne(targetEntity=RepositoryEntry.class,fetch=FetchType.LAZY,optional=false)
+	@JoinColumn(name="fk_entry", nullable=false, insertable=true, updatable=false)
+	private RepositoryEntry courseEntry;
+	@Column(name="l_subident", nullable=false, insertable=true, updatable=false)
+	private String subIdent;
+	@ManyToOne(targetEntity=IdentityImpl.class,fetch=FetchType.LAZY,optional=true)
+	@JoinColumn(name="fk_identity", nullable=false, insertable=true, updatable=false)
+	private Identity identity;
+	
+	@Override
+	public Long getKey() {
+		return key;
+	}
+
+	public void setKey(Long key) {
+		this.key = key;
+	}
+
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+
+	public void setCreationDate(Date creationDate) {
+		this.creationDate = creationDate;
+	}
+
+	@Override
+	public Date getLaunchDate() {
+		return launchDate;
+	}
+
+	public void setLaunchDate(Date launchDate) {
+		this.launchDate = launchDate;
+	}
+
+	@Override
+	public RepositoryEntry getCourseEntry() {
+		return courseEntry;
+	}
+
+	public void setCourseEntry(RepositoryEntry courseEntry) {
+		this.courseEntry = courseEntry;
+	}
+
+	@Override
+	public String getSubIdent() {
+		return subIdent;
+	}
+
+	public void setSubIdent(String subIdent) {
+		this.subIdent = subIdent;
+	}
+
+	@Override
+	public Identity getIdentity() {
+		return identity;
+	}
+
+	public void setIdentity(Identity identity) {
+		this.identity = identity;
+	}
+
+	@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;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		LaunchImpl other = (LaunchImpl) obj;
+		if (key == null) {
+			if (other.key != null)
+				return false;
+		} else if (!key.equals(other.key))
+			return false;
+		return true;
+	}
+	
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+	
+}
diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamRunController.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamRunController.java
index 7c9e09b0d30..5deee5313f2 100644
--- a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamRunController.java
+++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamRunController.java
@@ -35,8 +35,12 @@ import org.olat.core.util.resource.OresHelper;
 import org.olat.course.nodes.CourseNode;
 import org.olat.course.nodes.cal.CourseCalendars;
 import org.olat.course.nodes.livestream.LiveStreamSecurityCallback;
+import org.olat.course.nodes.livestream.LiveStreamService;
+import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.modules.ModuleConfiguration;
+import org.olat.repository.RepositoryEntry;
 import org.olat.resource.OLATResource;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * 
@@ -62,15 +66,18 @@ public class LiveStreamRunController extends BasicController {
 	
 	private final ModuleConfiguration moduleConfiguration;
 	private final String courseNodeIdent;
-	private final OLATResource courseOres;
+	private final UserCourseEnvironment userCourseEnv;
 	private final CourseCalendars calendars;
+	
+	@Autowired
+	private LiveStreamService liveStreamService;
 
 	public LiveStreamRunController(UserRequest ureq, WindowControl wControl, CourseNode coureNode,
-			OLATResource courseOres, LiveStreamSecurityCallback secCallback, CourseCalendars calendars) {
+			UserCourseEnvironment userCourseEnv, LiveStreamSecurityCallback secCallback, CourseCalendars calendars) {
 		super(ureq, wControl);
 		this.moduleConfiguration = coureNode.getModuleConfiguration();
 		this.courseNodeIdent = coureNode.getIdent();
-		this.courseOres = courseOres;
+		this.userCourseEnv = userCourseEnv;
 		this.calendars = calendars;
 		
 		mainVC = createVelocityContainer("run");
@@ -123,6 +130,8 @@ public class LiveStreamRunController extends BasicController {
 			streamsCtrl.refreshData(ureq.getUserSession());
 			addToHistory(ureq, streamsCtrl);
 		}
+		RepositoryEntry courseEntry = userCourseEnv.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
+		liveStreamService.createLaunch(courseEntry, courseNodeIdent, getIdentity());
 		segmentView.select(streamsLink);
 		mainVC.put("segmentCmp", streamsCtrl.getInitialComponent());
 	}
@@ -130,7 +139,8 @@ public class LiveStreamRunController extends BasicController {
 	private void doOpenStatistic(UserRequest ureq) {
 		if (statisticCtrl == null) {
 			WindowControl swControl = addToHistory(ureq, OresHelper.createOLATResourceableType(STATISTIC_RES_TYPE), null);
-			statisticCtrl = new LiveStreamStatisticController(ureq, swControl, courseOres, courseNodeIdent,
+			RepositoryEntry courseEntry = userCourseEnv.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
+			statisticCtrl = new LiveStreamStatisticController(ureq, swControl, courseEntry , courseNodeIdent,
 					moduleConfiguration, calendars);
 			listenTo(statisticCtrl);
 		} else {
@@ -144,6 +154,7 @@ public class LiveStreamRunController extends BasicController {
 	private void doOpenEdit(UserRequest ureq) {
 		if (editCtrl == null) {
 			WindowControl swControl = addToHistory(ureq, OresHelper.createOLATResourceableType(EDIT_RES_TYPE), null);
+			OLATResource courseOres = userCourseEnv.getCourseEnvironment().getCourseGroupManager().getCourseResource();
 			editCtrl = new WeeklyCalendarController(ureq, swControl, calendars.getCalendars(),
 					WeeklyCalendarController.CALLER_LIVE_STREAM, courseOres, false);
 			editCtrl.setDifferentiateManagedEvent(true);
diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamStatisticController.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamStatisticController.java
index 86b42622789..d8b33f2f5f6 100644
--- a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamStatisticController.java
+++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamStatisticController.java
@@ -38,7 +38,7 @@ import org.olat.course.nodes.livestream.LiveStreamEvent;
 import org.olat.course.nodes.livestream.LiveStreamService;
 import org.olat.course.nodes.livestream.ui.LiveStreamEventDataModel.EventCols;
 import org.olat.modules.ModuleConfiguration;
-import org.olat.resource.OLATResource;
+import org.olat.repository.RepositoryEntry;
 import org.springframework.beans.factory.annotation.Autowired;
 
 /**
@@ -52,7 +52,7 @@ public class LiveStreamStatisticController extends FormBasicController {
 	private FlexiTableElement tableEl;
 	private LiveStreamEventDataModel dataModel;
 	
-	private String courseResId;
+	private RepositoryEntry courseEntry;
 	private String courseNodeIdent;
 	private final CourseCalendars calendars;
 	private final int bufferBeforeMin;
@@ -60,10 +60,10 @@ public class LiveStreamStatisticController extends FormBasicController {
 	@Autowired
 	private LiveStreamService liveStreamService;
 
-	public LiveStreamStatisticController(UserRequest ureq, WindowControl wControl, OLATResource courseOres,
+	public LiveStreamStatisticController(UserRequest ureq, WindowControl wControl, RepositoryEntry courseEntry,
 			String courseNodeIdent, ModuleConfiguration moduleConfiguration, CourseCalendars calendars) {
 		super(ureq, wControl, LAYOUT_VERTICAL);
-		this.courseResId = courseOres.getResourceableId().toString();
+		this.courseEntry = courseEntry;
 		this.courseNodeIdent = courseNodeIdent;
 		this.calendars = calendars;
 		
@@ -101,7 +101,7 @@ public class LiveStreamStatisticController extends FormBasicController {
 		List<LiveStreamEventRow> rows = new ArrayList<>(upcomingEvents.size());
 		for (LiveStreamEvent liveStreamEvent : upcomingEvents) {
 			LiveStreamEventRow row = new LiveStreamEventRow(liveStreamEvent);
-			Long viewers = liveStreamService.getViewers(courseResId, courseNodeIdent, liveStreamEvent.getBegin(),
+			Long viewers = liveStreamService.getLaunchers(courseEntry, courseNodeIdent, liveStreamEvent.getBegin(),
 					liveStreamEvent.getEnd());
 			row.setViewers(viewers);
 			rows.add(row);
diff --git a/src/main/java/org/olat/upgrade/OLATUpgrade_14_2_0.java b/src/main/java/org/olat/upgrade/OLATUpgrade_14_2_0.java
index 74b1eb999ff..1c94a3b4776 100644
--- a/src/main/java/org/olat/upgrade/OLATUpgrade_14_2_0.java
+++ b/src/main/java/org/olat/upgrade/OLATUpgrade_14_2_0.java
@@ -22,9 +22,13 @@ package org.olat.upgrade;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.logging.log4j.Logger;
+import org.olat.basesecurity.BaseSecurity;
 import org.olat.commons.info.notification.InfoSubscription;
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.commons.persistence.QueryBuilder;
@@ -35,12 +39,16 @@ import org.olat.core.commons.services.notifications.model.SubscriberImpl;
 import org.olat.core.id.Identity;
 import org.olat.core.id.Organisation;
 import org.olat.core.logging.Tracing;
+import org.olat.core.logging.activity.LoggingObject;
 import org.olat.core.util.prefs.Preferences;
 import org.olat.core.util.prefs.db.DbStorage;
+import org.olat.course.nodes.livestream.manager.LiveStreamLaunchDAO;
 import org.olat.modules.quality.QualityDataCollection;
 import org.olat.modules.quality.QualityService;
 import org.olat.properties.Property;
+import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryService;
+import org.olat.repository.manager.RepositoryEntryDAO;
 import org.olat.repository.model.RepositoryEntryRefImpl;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -59,6 +67,7 @@ public class OLATUpgrade_14_2_0 extends OLATUpgrade {
 	private static final String VERSION = "OLAT_14.2.0";
 	private static final String TRANSFER_INFO_NOT_DESIRED = "TRANSFER INFOS NOTIFICATIONS NOT DESIRED";
 	private static final String DATA_COLLECTION_ORGANISATIONS = "DATA COLLECTION ORGANISATIONS";
+	private static final String LIVE_STREAM_LAUNCHES = "LIVE STREAM LAUNCHES";
 	
 	@Autowired
 	private DB dbInstance;
@@ -71,6 +80,11 @@ public class OLATUpgrade_14_2_0 extends OLATUpgrade {
 	@Autowired
 	private RepositoryService repositoryService;
 	@Autowired
+	private RepositoryEntryDAO repositoryEntryDao;
+	@Autowired
+	private LiveStreamLaunchDAO liveStreamLaunchDao;
+	@Autowired
+	private BaseSecurity securityManager;
 	
 	public OLATUpgrade_14_2_0() {
 		super();
@@ -94,6 +108,7 @@ public class OLATUpgrade_14_2_0 extends OLATUpgrade {
 		boolean allOk = true;
 		allOk &= migrateInfosNotificationsNotDesired(upgradeManager, uhd);
 		allOk &= migrateDataCollectionOrganisations(upgradeManager, uhd);
+		allOk &= migrateLiveStreamLaunches(upgradeManager, uhd);
 
 		uhd.setInstallationComplete(allOk);
 		upgradeManager.setUpgradesHistory(uhd, VERSION);
@@ -272,4 +287,97 @@ public class OLATUpgrade_14_2_0 extends OLATUpgrade {
 		Organisation organisation = dataCollection.getTopicCurriculumElement().getCurriculum().getOrganisation();
 		qualityService.updateDataCollectionOrganisations(dataCollection, Collections.singletonList(organisation));
 	}
+	
+	private boolean migrateLiveStreamLaunches(UpgradeManager upgradeManager, UpgradeHistoryData uhd) {
+		boolean allOk = true;
+		if (!uhd.getBooleanDataValue(LIVE_STREAM_LAUNCHES)) {
+			try {
+				deleteLaunches(); // to avoid duplicates if migrations runs a second time
+				List<LoggingObject> loggedLaunches = getLoggedLaunches();
+				migrateLoggedLaunches(loggedLaunches);
+				log.info("Live stream launches migrated.");
+			} catch (Exception e) {
+				log.error("", e);
+				allOk = false;
+			}
+			uhd.setBooleanDataValue(LIVE_STREAM_LAUNCHES, allOk);
+			upgradeManager.setUpgradesHistory(uhd, VERSION);
+		}
+		return allOk;
+	}
+
+	private void deleteLaunches() {
+		String query = "delete from livestreamlaunch";
+		dbInstance.getCurrentEntityManager().createQuery(query).executeUpdate();
+		dbInstance.commitAndCloseSession();
+	}
+
+	private List<LoggingObject> getLoggedLaunches() {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("select log");
+		sb.append("  from loggingobject log");
+		sb.and().append("log.actionVerb = 'launch'");
+		sb.and().append("log.targetResType = 'livestream'");
+		
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), LoggingObject.class)
+				.getResultList();
+	}
+	private void migrateLoggedLaunches(List<LoggingObject> loggedLaunches) {
+		log.info("Migraton of {} logged live stream launches started.", loggedLaunches.size());
+		
+		Map<Long, Identity> identityCache = new HashMap<>();
+		Map<String, RepositoryEntry> courseEntryCache = new HashMap<>();
+		AtomicInteger migrationCounter = new AtomicInteger(0);
+		for (LoggingObject loggingObject : loggedLaunches) {
+			try {
+				migrateLaunch(loggingObject, identityCache, courseEntryCache);
+				migrationCounter.incrementAndGet();
+			} catch (Exception e) {
+				log.warn("Live stream launch not migrated. Id={}", loggingObject.getKey());
+			}
+			if(migrationCounter.get() % 25 == 0) {
+				dbInstance.commitAndCloseSession();
+			} else {
+				dbInstance.commit();
+			}
+			if(migrationCounter.get() % 100 == 0) {
+				log.info("Live stream: num. of launches migrated: {}", migrationCounter);
+			}
+		}
+	}
+
+	private void migrateLaunch(LoggingObject loggingObject, Map<Long, Identity> identityCache, Map<String, RepositoryEntry> courseEntryCache) {
+		Long userId = Long.valueOf(loggingObject.getUserId());
+		Identity identity = identityCache.get(userId);
+		if (identity == null) {
+			identity = securityManager.loadIdentityByKey(userId);
+			if (identity != null) {
+				identityCache.put(userId, identity);
+			} else {
+				log.warn("Live stream launch migrated: No identity found. logId={}, courseResId={}", loggingObject.getKey(), userId);
+			}
+		}
+		
+		String courseResId = loggingObject.getParentResId();
+		RepositoryEntry courseEntry = courseEntryCache.get(courseResId);
+		if (courseEntry == null) {
+			courseEntry = repositoryEntryDao.loadByResourceId("CourseModule", Long.valueOf(courseResId));
+			if (courseEntry != null) {
+				courseEntryCache.put(courseResId, courseEntry);
+			} else {
+				log.warn("Live stream launch migrated: No course entry found. logId={}, courseResId={}", loggingObject.getKey(), courseResId);
+			}
+		}
+
+		String subIdent = loggingObject.getTargetResId();
+		Date launchDate = loggingObject.getCreationDate();
+		if (identity != null && courseEntry != null) {
+			liveStreamLaunchDao.create(courseEntry, subIdent, identity, launchDate);
+			log.debug("Live stream launch migrated. Id={}", loggingObject.getKey());
+		} else {
+			log.warn("Live stream launch not migrated. Id={}", loggingObject.getKey());
+		}
+	}
+
 }
diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml
index a6f62ae0fc8..0c948fef958 100644
--- a/src/main/resources/META-INF/persistence.xml
+++ b/src/main/resources/META-INF/persistence.xml
@@ -116,6 +116,7 @@
 		<class>org.olat.course.nodes.gta.model.TaskListImpl</class>
 		<class>org.olat.course.nodes.gta.model.TaskRevisionImpl</class>
 		<class>org.olat.course.nodes.gta.model.TaskRevisionDateImpl</class>
+		<class>org.olat.course.nodes.livestream.model.LaunchImpl</class>
 		<class>org.olat.course.certificate.model.CertificateImpl</class>
 		<class>org.olat.course.certificate.model.CertificateStandalone</class>
 		<class>org.olat.course.certificate.model.CertificateLightImpl</class>
diff --git a/src/main/resources/database/mysql/alter_14_1_x_to_14_2_0.sql b/src/main/resources/database/mysql/alter_14_1_x_to_14_2_0.sql
index 6de790bfa5b..6bbd4c40d97 100644
--- a/src/main/resources/database/mysql/alter_14_1_x_to_14_2_0.sql
+++ b/src/main/resources/database/mysql/alter_14_1_x_to_14_2_0.sql
@@ -16,6 +16,19 @@ alter table o_gta_task_revision ENGINE = InnoDB;
 alter table o_gta_task_revision add constraint task_rev_to_task_idx foreign key (fk_task) references o_gta_task (id);
 alter table o_gta_task_revision add constraint task_rev_to_ident_idx foreign key (fk_comment_author) references o_bs_identity (id);
 
+-- livestream
+create table o_livestream_launch (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   l_launch_date datetime not null,
+   fk_entry bigint not null,
+   l_subident varchar(128) not null,
+   fk_identity bigint not null,
+   primary key (id)
+);
+alter table o_livestream_launch ENGINE = InnoDB;
+create index idx_livestream_viewers_idx on o_livestream_launch(l_subident, l_launch_date, fk_entry, fk_identity);
+
 
 -- notifications
 alter table o_noti_sub add column subenabled bit default 1;
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index 7c05dd24ad5..24713352e9f 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -2918,6 +2918,17 @@ create table o_es_usage (
    primary key (id)
 );
 
+-- livestream
+create table o_livestream_launch (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   l_launch_date datetime not null,
+   fk_entry bigint not null,
+   l_subident varchar(128) not null,
+   fk_identity bigint not null,
+   primary key (id)
+);
+
 
 -- user view
 create view o_bs_identity_short_v as (
@@ -3316,6 +3327,7 @@ alter table o_cur_curriculum_element ENGINE = InnoDB;
 alter table o_cur_element_type_to_type ENGINE = InnoDB;
 alter table o_cur_element_to_tax_level ENGINE = InnoDB;
 alter table o_es_usage ENGINE = InnoDB;
+alter table o_livestream_launch ENGINE = InnoDB;
 
 -- rating
 alter table o_userrating add constraint FKF26C8375236F20X foreign key (creator_id) references o_bs_identity (id);
@@ -4009,6 +4021,10 @@ create index log_gptarget_resid_idx on o_loggingtable(grandparentresid);
 create index log_ggptarget_resid_idx on o_loggingtable(greatgrandparentresid);
 create index log_creationdate_idx on o_loggingtable(creationdate);
 
+-- livestream
+create index idx_livestream_viewers_idx on o_livestream_launch(l_subident, l_launch_date, fk_entry, fk_identity);
+
+
 
 insert into hibernate_unique_key values ( 0 );
 SET FOREIGN_KEY_CHECKS = 1;
diff --git a/src/main/resources/database/oracle/alter_14_1_x_to_14_2_0.sql b/src/main/resources/database/oracle/alter_14_1_x_to_14_2_0.sql
index cd0b42f81b4..d4373ddfc96 100644
--- a/src/main/resources/database/oracle/alter_14_1_x_to_14_2_0.sql
+++ b/src/main/resources/database/oracle/alter_14_1_x_to_14_2_0.sql
@@ -17,6 +17,18 @@ create index idx_task_rev_to_task_idx on o_gta_task_revision (fk_task);
 alter table o_gta_task_revision add constraint task_rev_to_ident_idx foreign key (fk_comment_author) references o_bs_identity (id);
 create index idx_task_rev_to_ident_idx on o_gta_task_revision (fk_comment_author);
 
+-- livestream
+create table o_livestream_launch (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   l_launch_date timestamp not null,
+   fk_entry  number(20) not null,
+   l_subident varchar(128) not null,
+   fk_identity  number(20) not null,
+   primary key (id)
+);
+create index idx_livestream_viewers_idx on o_livestream_launch(l_subident, l_launch_date, fk_entry, fk_identity);
+
 
 -- notifications
 alter table o_noti_sub add subenabled number default 1;
diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql
index cdcce51147a..6efb4887532 100644
--- a/src/main/resources/database/oracle/setupDatabase.sql
+++ b/src/main/resources/database/oracle/setupDatabase.sql
@@ -3001,6 +3001,18 @@ create table o_es_usage (
    primary key (id)
 );
 
+-- livestream
+create table o_livestream_launch (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   l_launch_date timestamp not null,
+   fk_entry  number(20) not null,
+   l_subident varchar(128) not null,
+   fk_identity  number(20) not null,
+   primary key (id)
+);
+
+
 -- user view
 create view o_bs_identity_short_v as (
    select
@@ -4217,6 +4229,9 @@ create index log_gptarget_resid_idx on o_loggingtable(grandparentresid);
 create index log_ggptarget_resid_idx on o_loggingtable(greatgrandparentresid);
 create index log_creationdate_idx on o_loggingtable(creationdate);
 
+-- livestream
+create index idx_livestream_viewers_idx on o_livestream_launch(l_subident, l_launch_date, fk_entry, fk_identity);
+
 
 insert into o_stat_lastupdated (until_datetime, from_datetime, lastupdated) values (to_date('1999-01-01', 'YYYY-mm-dd'), to_date('1999-01-01', 'YYYY-mm-dd'), to_date('1999-01-01', 'YYYY-mm-dd'));
 insert into hibernate_unique_key values ( 0 );
diff --git a/src/main/resources/database/postgresql/alter_14_1_x_to_14_2_0.sql b/src/main/resources/database/postgresql/alter_14_1_x_to_14_2_0.sql
index 59e40c347e7..fd598c9601b 100644
--- a/src/main/resources/database/postgresql/alter_14_1_x_to_14_2_0.sql
+++ b/src/main/resources/database/postgresql/alter_14_1_x_to_14_2_0.sql
@@ -18,7 +18,16 @@ alter table o_gta_task_revision add constraint task_rev_to_ident_idx foreign key
 create index idx_task_rev_to_ident_idx on o_gta_task_revision (fk_comment_author);
 
 -- livestream
-create index idx_log_livestream_idx on o_loggingtable(targetresid, creationdate, parentresid, user_id) where actionverb = 'launch' and targetrestype = 'livestream';
+create table o_livestream_launch (
+   id bigserial,
+   creationdate timestamp not null,
+   l_launch_date timestamp not null,
+   fk_entry int8 not null,
+   l_subident varchar(128) not null,
+   fk_identity int8 not null,
+   primary key (id)
+);
+create index idx_livestream_viewers_idx on o_livestream_launch(l_subident, l_launch_date, fk_entry, fk_identity);
 
 
 -- notifications
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index 63905caeb91..d24d7c2b969 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -2944,6 +2944,17 @@ create table o_es_usage (
    primary key (id)
 );
 
+-- livestream
+create table o_livestream_launch (
+   id bigserial,
+   creationdate timestamp not null,
+   l_launch_date timestamp not null,
+   fk_entry int8 not null,
+   l_subident varchar(128) not null,
+   fk_identity int8 not null,
+   primary key (id)
+);
+
 -- user view
 create view o_bs_identity_short_v as (
    select
@@ -4108,8 +4119,9 @@ create index log_ptarget_resid_idx on o_loggingtable(parentresid);
 create index log_gptarget_resid_idx on o_loggingtable(grandparentresid);
 create index log_ggptarget_resid_idx on o_loggingtable(greatgrandparentresid);
 create index log_creationdate_idx on o_loggingtable(creationdate);
-create index idx_log_livestream_idx on o_loggingtable(targetresid, creationdate, parentresid, user_id) where actionverb = 'launch' and targetrestype = 'livestream';
 
+-- livestream
+create index idx_livestream_viewers_idx on o_livestream_launch(l_subident, l_launch_date, fk_entry, fk_identity);
 
 
 insert into hibernate_unique_key values ( 0 );
diff --git a/src/test/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAOTest.java b/src/test/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAOTest.java
new file mode 100644
index 00000000000..24aa977885a
--- /dev/null
+++ b/src/test/java/org/olat/course/nodes/livestream/manager/LiveStreamLaunchDAOTest.java
@@ -0,0 +1,100 @@
+/**
+ * <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.livestream.manager;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.within;
+import static org.olat.test.JunitTestHelper.random;
+
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import org.junit.Test;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.Identity;
+import org.olat.course.nodes.livestream.Launch;
+import org.olat.repository.RepositoryEntry;
+import org.olat.test.JunitTestHelper;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 17 Dec 2019<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class LiveStreamLaunchDAOTest extends OlatTestCase {
+	
+	@Autowired
+	private LiveStreamLaunchDAO sut;
+	@Autowired
+	private DB dbInstance;
+	
+	@Test
+	public void shouldCreateLaunch() {
+		RepositoryEntry entry = JunitTestHelper.createAndPersistRepositoryEntry();
+		String subIdent = JunitTestHelper.random();
+		Identity identity = JunitTestHelper.createAndPersistIdentityAsRndUser("ls");
+		Date launchDate = new Date();
+		dbInstance.commitAndCloseSession();
+		
+		Launch launch = sut.create(entry, subIdent, identity, launchDate);
+		dbInstance.commitAndCloseSession();
+		
+		assertThat(launch.getCreationDate()).isNotNull();
+		assertThat(launch.getLaunchDate()).isCloseTo(launchDate, within(1000, ChronoUnit.MILLIS).getValue());
+		assertThat(launch.getCourseEntry()).isEqualTo(entry);
+		assertThat(launch.getSubIdent()).isEqualTo(subIdent);
+		assertThat(launch.getIdentity()).isEqualTo(identity);
+	}
+	
+	@Test
+	public void shouldGetLaunchers() {
+		Identity identity1 = JunitTestHelper.createAndPersistIdentityAsRndUser(random());
+		Identity identity2 = JunitTestHelper.createAndPersistIdentityAsRndUser(random());
+		Identity identityOther = JunitTestHelper.createAndPersistIdentityAsRndUser(random());
+		RepositoryEntry courseEntry = JunitTestHelper.createAndPersistRepositoryEntry();
+		RepositoryEntry courseEntryOther = JunitTestHelper.createAndPersistRepositoryEntry();
+		String subIdent = random();
+		
+		Date before = new GregorianCalendar(2010, 2, 8).getTime();
+		Date from = new GregorianCalendar(2010, 2, 9).getTime();
+		Date inside = new GregorianCalendar(2010, 2, 10).getTime();
+		Date to = new GregorianCalendar(2010, 2, 11).getTime();
+		Date after = new GregorianCalendar(2010, 2, 12).getTime();
+		sut.create(courseEntry, subIdent, identity1, inside);
+		sut.create(courseEntry, subIdent, identity1, inside);
+		sut.create(courseEntry, subIdent, identity1, inside);
+		sut.create(courseEntry, subIdent, identity2, inside);
+		// These log entries should have all wrong parameters. So userKeyOther should not be a viewer.
+		sut.create(courseEntryOther, subIdent, identityOther, inside);
+		sut.create(courseEntry, random(), identityOther, inside);
+		sut.create(courseEntry, subIdent, identityOther, before);
+		sut.create(courseEntry, subIdent, identityOther, after);
+		dbInstance.commitAndCloseSession();
+		
+		Long viewers = sut.getLaunchers(courseEntry, subIdent, from, to);
+		
+		assertThat(viewers).isEqualTo(2);
+	}
+
+}
diff --git a/src/test/java/org/olat/course/nodes/livestream/manager/LiveStreamStatisticDAOTest.java b/src/test/java/org/olat/course/nodes/livestream/manager/LiveStreamStatisticDAOTest.java
deleted file mode 100644
index ce47808ec6d..00000000000
--- a/src/test/java/org/olat/course/nodes/livestream/manager/LiveStreamStatisticDAOTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/**
- * <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.livestream.manager;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.olat.test.JunitTestHelper.random;
-
-import java.util.Date;
-import java.util.GregorianCalendar;
-
-import org.junit.Test;
-import org.olat.core.commons.persistence.DB;
-import org.olat.core.logging.activity.LoggingObject;
-import org.olat.test.OlatTestCase;
-import org.springframework.beans.factory.annotation.Autowired;
-
-/**
- * 
- * Initial date: 17 Dec 2019<br>
- * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
- *
- */
-public class LiveStreamStatisticDAOTest extends OlatTestCase {
-	
-	@Autowired
-	private LiveStreamStatisticDAO sut;
-	@Autowired
-	private DB dbInstance;
-	
-	@Test
-	public void shouldGetViewers() {
-		Long userKey1 = 132L;
-		Long userKey2 = 1324L;
-		Long userKeyOther = 13245L;
-		String courseResId = "courseResId";
-		String nodeIdent = "nodeIdent";
-		Date before = new GregorianCalendar(2010, 2, 8).getTime();
-		Date from = new GregorianCalendar(2010, 2, 9).getTime();
-		Date inside = new GregorianCalendar(2010, 2, 10).getTime();
-		Date to = new GregorianCalendar(2010, 2, 11).getTime();
-		Date after = new GregorianCalendar(2010, 2, 12).getTime();
-		createLoggingObject("launch", courseResId, "livestream", nodeIdent, userKey1, inside);
-		createLoggingObject("launch", courseResId, "livestream", nodeIdent, userKey1, inside);
-		createLoggingObject("launch", courseResId, "livestream", nodeIdent, userKey1, inside);
-		createLoggingObject("launch", courseResId, "livestream", nodeIdent, userKey2, inside);
-		// These log entries should have all wrong parameters. So userKeyOther should not be a viewer.
-		createLoggingObject("OTHER", courseResId, "livestream", nodeIdent, userKeyOther, inside);
-		createLoggingObject("launch", "OTHER", "livestream", nodeIdent, userKeyOther, inside);
-		createLoggingObject("launch", courseResId, "OTHER", nodeIdent, userKeyOther, inside);
-		createLoggingObject("launch", courseResId, "livestream", "OTHER", userKeyOther, inside);
-		createLoggingObject("launch", courseResId, "livestream", nodeIdent, userKeyOther, before);
-		createLoggingObject("launch", courseResId, "livestream", nodeIdent, userKeyOther, after);
-		dbInstance.commitAndCloseSession();
-		
-		Long viewers = sut.getViewers(courseResId, nodeIdent, from, to);
-		
-		assertThat(viewers).isEqualTo(2);
-	}
-
-	private void createLoggingObject(String actionVerb, String parentResId, String targetResType, String targetResId,
-			Long identityKey, Date creationDate) {
-		LoggingObject logObj = new LoggingObject(random(), identityKey, "r", actionVerb, "node");
-		logObj.setCreationDate(creationDate);
-		logObj.setParentResId(parentResId);
-		logObj.setTargetResType(targetResType);
-		logObj.setTargetResId(targetResId);
-		logObj.setResourceAdminAction(Boolean.TRUE);
-		dbInstance.saveObject(logObj);
-	}
-
-
-}
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index 7f9b308f63f..83f603d82ae 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -179,7 +179,7 @@ import org.junit.runners.Suite;
 	org.olat.course.nodes.gta.manager.GTATaskRevisionDAOTest.class,
 	org.olat.course.nodes.gta.manager.GTAIdentityMarkDAOTest.class,
 	org.olat.course.nodes.gta.rule.GTAReminderRuleTest.class,
-	org.olat.course.nodes.livestream.manager.LiveStreamStatisticDAOTest.class,
+	org.olat.course.nodes.livestream.manager.LiveStreamLaunchDAOTest.class,
 	org.olat.course.nodes.members.manager.MembersManagerTest.class,
 	org.olat.course.nodes.pf.manager.PFManagerTest.class,
 	org.olat.course.assessment.AssessmentManagerTest.class,
-- 
GitLab