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