From c848f09f704158f5dc9cd16f25bf37d8b90363f6 Mon Sep 17 00:00:00 2001
From: srosse <stephane.rosse@frentix.com>
Date: Thu, 6 Aug 2020 10:50:43 +0200
Subject: [PATCH] OO-4819: persist the list of attendee of BigBlueButton
 meetings

---
 .../bigbluebutton/BigBlueButtonAttendee.java  |  48 +++++
 .../BigBlueButtonAttendeeRoles.java           |  47 +++++
 .../bigbluebutton/BigBlueButtonManager.java   |   2 +-
 .../manager/BigBlueButtonAttendeeDAO.java     | 129 ++++++++++++
 .../manager/BigBlueButtonManagerImpl.java     |  26 ++-
 .../model/BigBlueButtonAttendeeImpl.java      | 183 ++++++++++++++++++
 .../ui/BigBlueButtonGuestJoinController.java  |  11 +-
 .../ui/BigBlueButtonMeetingController.java    |   9 +-
 src/main/resources/META-INF/persistence.xml   |   1 +
 .../database/mysql/alter_15_1_x_to_15_2_0.sql |  20 ++
 .../database/mysql/setupDatabase.sql          |  16 ++
 .../oracle/alter_15_1_x_to_15_2_0.sql         |  19 ++
 .../database/oracle/setupDatabase.sql         |  19 +-
 .../postgresql/alter_15_1_x_to_15_2_0.sql     |  20 ++
 .../database/postgresql/setupDatabase.sql     |  17 ++
 .../manager/BigBlueButtonAttendeeDAOTest.java | 142 ++++++++++++++
 .../java/org/olat/test/AllTestsJunit4.java    |   1 +
 17 files changed, 702 insertions(+), 8 deletions(-)
 create mode 100644 src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonAttendee.java
 create mode 100644 src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonAttendeeRoles.java
 create mode 100644 src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonAttendeeDAO.java
 create mode 100644 src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonAttendeeImpl.java
 create mode 100644 src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonAttendeeDAOTest.java

diff --git a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonAttendee.java b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonAttendee.java
new file mode 100644
index 00000000000..a6edf955409
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonAttendee.java
@@ -0,0 +1,48 @@
+/**
+ * <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.modules.bigbluebutton;
+
+import java.util.Date;
+
+import org.olat.core.id.CreateInfo;
+import org.olat.core.id.Identity;
+import org.olat.core.id.ModifiedInfo;
+
+/**
+ * 
+ * Initial date: 6 août 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface BigBlueButtonAttendee extends ModifiedInfo, CreateInfo {
+
+	public Long getKey();
+	
+	public Date getJoinDate();
+	
+	public BigBlueButtonAttendeeRoles getRolesEnum();
+	
+	public String getPseudo();
+	
+	public Identity getIdentity();
+	
+	public BigBlueButtonMeeting getMeeting();
+	
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonAttendeeRoles.java b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonAttendeeRoles.java
new file mode 100644
index 00000000000..51d1b70b385
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonAttendeeRoles.java
@@ -0,0 +1,47 @@
+
+/**
+ * <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.modules.bigbluebutton;
+
+/**
+ * 
+ * Initial date: 6 août 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public enum BigBlueButtonAttendeeRoles {
+	
+	moderator,
+	viewer,
+	guest,
+	external;
+	
+	public static BigBlueButtonAttendeeRoles valueSecure(String val) {
+		if(val == null) return null;
+		
+		for(BigBlueButtonAttendeeRoles role:values()) {
+			if(role.name().equals(val)) {
+				return role;
+			}
+		}
+		return null;
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java
index 8393e721861..70b27046daf 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java
@@ -143,7 +143,7 @@ public interface BigBlueButtonManager {
 	
 	public List<BigBlueButtonMeeting> getAllMeetings();
 	
-	public String join(BigBlueButtonMeeting meeting, Identity identity, String pseudo, boolean moderator, boolean guest,
+	public String join(BigBlueButtonMeeting meeting, Identity identity, String pseudo, BigBlueButtonAttendeeRoles role,
 			Boolean isRunning, BigBlueButtonErrors errors);
 	
 	public boolean isMeetingRunning(BigBlueButtonMeeting meeting);
diff --git a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonAttendeeDAO.java b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonAttendeeDAO.java
new file mode 100644
index 00000000000..c33d0a69f89
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonAttendeeDAO.java
@@ -0,0 +1,129 @@
+/**
+ * <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.modules.bigbluebutton.manager;
+
+import java.util.Date;
+import java.util.List;
+
+import org.olat.basesecurity.IdentityRef;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.Identity;
+import org.olat.core.util.StringHelper;
+import org.olat.modules.bigbluebutton.BigBlueButtonAttendee;
+import org.olat.modules.bigbluebutton.BigBlueButtonAttendeeRoles;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+import org.olat.modules.bigbluebutton.model.BigBlueButtonAttendeeImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 6 août 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class BigBlueButtonAttendeeDAO {
+	
+	@Autowired
+	private DB dbInstance;
+	
+	public BigBlueButtonAttendee createAttendee(Identity identity, String pseudo, BigBlueButtonAttendeeRoles roles,
+			Date joinDate, BigBlueButtonMeeting meeting) {
+		BigBlueButtonAttendeeImpl attendee = new BigBlueButtonAttendeeImpl();
+		attendee.setCreationDate(new Date());
+		attendee.setLastModified(attendee.getCreationDate());
+		if(roles != null) {
+			attendee.setRole(roles.name());
+		}
+		attendee.setJoinDate(joinDate);
+		attendee.setPseudo(pseudo);
+		if(roles == BigBlueButtonAttendeeRoles.moderator || roles == BigBlueButtonAttendeeRoles.viewer) {
+			attendee.setIdentity(identity);
+		}
+		attendee.setMeeting(meeting);
+		dbInstance.getCurrentEntityManager().persist(attendee);
+		return attendee;
+	}
+	
+	public boolean hasAttendee(IdentityRef identity, BigBlueButtonMeeting meeting) {
+		if(identity == null) return false;
+
+		List<Long> attendeeKeys = dbInstance.getCurrentEntityManager()
+			.createNamedQuery("hasAttendeeByIdentity", Long.class)
+			.setParameter("identityKey", identity.getKey())
+			.setParameter("meetingKey", meeting.getKey())
+			.setFirstResult(0)
+			.setMaxResults(1)
+			.getResultList();
+		return attendeeKeys != null && !attendeeKeys.isEmpty()
+				&& attendeeKeys.get(0) != null && attendeeKeys.get(0).intValue() > 0;
+	}
+	
+	public boolean hasAttendee(String pseudo, BigBlueButtonMeeting meeting) {
+		if(!StringHelper.containsNonWhitespace(pseudo)) return false;
+
+		StringBuilder sb = new StringBuilder();
+		sb.append("select attendee.key from bigbluebuttonattendee as attendee")
+		  .append(" where attendee.meeting.key=:meetingKey and lower(attendee.pseudo)=:pseudo");
+		
+		List<Long> attendeeKeys = dbInstance.getCurrentEntityManager()
+			.createQuery(sb.toString(), Long.class)
+			.setParameter("pseudo", pseudo.toLowerCase())
+			.setParameter("meetingKey", meeting.getKey())
+			.setFirstResult(0)
+			.setMaxResults(1)
+			.getResultList();
+		return attendeeKeys != null && !attendeeKeys.isEmpty()
+				&& attendeeKeys.get(0) != null && attendeeKeys.get(0).intValue() > 0;
+	}
+	
+	public BigBlueButtonAttendee getAttendee(IdentityRef identity, BigBlueButtonMeeting meeting) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select attendee from bigbluebuttonattendee as attendee")
+		  .append(" inner join fetch attendee.identity as ident")
+		  .append(" where ident.key=:identityKey and attendee.meeting.key=:meetingKey");
+		
+		List<BigBlueButtonAttendee> attendees = dbInstance.getCurrentEntityManager()
+			.createQuery(sb.toString(), BigBlueButtonAttendee.class)
+			.setParameter("identityKey", identity.getKey())
+			.setParameter("meetingKey", meeting.getKey())
+			.setFirstResult(0)
+			.setMaxResults(1)
+			.getResultList();
+		return attendees == null || attendees.isEmpty() ? null : attendees.get(0);
+	}
+	
+	public int deleteAttendee(BigBlueButtonMeeting meeting) {
+		String query = "delete from bigbluebuttonattendee as attendee where attendee.meeting.key=:meetingKey";
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(query)
+				.setParameter("meetingKey", meeting.getKey())
+				.executeUpdate();
+	}
+	
+	public int deleteAttendee(IdentityRef identity) {
+		String query = "delete from bigbluebuttonattendee as attendee where attendee.identity.key=:identityKey";
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(query)
+				.setParameter("identityKey", identity.getKey())
+				.executeUpdate();
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java
index c37c6c3e350..ce3d5bcf31b 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java
@@ -58,6 +58,7 @@ import org.olat.course.ICourse;
 import org.olat.group.BusinessGroup;
 import org.olat.group.BusinessGroupService;
 import org.olat.group.DeletableGroupData;
+import org.olat.modules.bigbluebutton.BigBlueButtonAttendeeRoles;
 import org.olat.modules.bigbluebutton.BigBlueButtonManager;
 import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
 import org.olat.modules.bigbluebutton.BigBlueButtonMeetingLayoutEnum;
@@ -80,6 +81,7 @@ import org.olat.repository.RepositoryEntryRef;
 import org.olat.repository.RepositoryEntrySecurity;
 import org.olat.repository.RepositoryManager;
 import org.olat.repository.manager.RepositoryEntryDAO;
+import org.olat.user.UserDataDeletable;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -94,7 +96,7 @@ import org.w3c.dom.Document;
  */
 @Service
 public class BigBlueButtonManagerImpl implements BigBlueButtonManager,
-	DeletableGroupData, RepositoryEntryDataDeletable, InitializingBean {
+	DeletableGroupData, RepositoryEntryDataDeletable, UserDataDeletable, InitializingBean {
 	
 	private static final Logger log = Tracing.createLoggerFor(BigBlueButtonManagerImpl.class);
 
@@ -115,6 +117,8 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager,
 	@Autowired
 	private BigBlueButtonMeetingDAO bigBlueButtonMeetingDao;
 	@Autowired
+	private BigBlueButtonAttendeeDAO bigBlueButtonAttendeeDao;
+	@Autowired
 	private BigBlueButtonMeetingTemplateDAO bigBlueButtonMeetingTemplateDao;
 	@Autowired @Qualifier("native")
 	private BigBlueButtonRecordingsHandler defaultRecordingsHandler;
@@ -265,6 +269,11 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager,
 		}
 		return !errors.hasErrors();
 	}
+	
+	@Override
+	public void deleteUserData(Identity identity, String newDeletedUserName) {
+		bigBlueButtonAttendeeDao.deleteAttendee(identity);
+	}
 
 	@Override
 	public BigBlueButtonServer createServer(String url, String recordingUrl, String sharedSecret) {
@@ -431,6 +440,7 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager,
 		if(reloadedMeeting != null) {
 			removeCalendarEvent(reloadedMeeting);
 			deleteRecordings(meeting, errors);
+			bigBlueButtonAttendeeDao.deleteAttendee(reloadedMeeting);
 			bigBlueButtonMeetingDao.deleteMeeting(reloadedMeeting);
 		}
 		return false;
@@ -725,8 +735,11 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager,
 	}
 
 	@Override
-	public String join(BigBlueButtonMeeting meeting, Identity identity, String pseudo, boolean moderator, boolean guest, Boolean isRunning, BigBlueButtonErrors errors) {
+	public String join(BigBlueButtonMeeting meeting, Identity identity, String pseudo, BigBlueButtonAttendeeRoles role, Boolean isRunning, BigBlueButtonErrors errors) {
 		String joinUrl = null;
+		boolean moderator = role == BigBlueButtonAttendeeRoles.moderator;
+		boolean guest = false;
+		
 		if(isRunning != null && isRunning.booleanValue() && meeting.getServer() != null) {
 			joinUrl = buildJoinUrl(meeting, meeting.getServer(), identity, pseudo, moderator, guest);
 		} else {
@@ -735,6 +748,15 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager,
 				joinUrl = buildJoinUrl(meeting, meeting.getServer(), identity, pseudo, moderator, guest);
 			}
 		}
+		if(StringHelper.containsNonWhitespace(joinUrl)) {
+			if((role == BigBlueButtonAttendeeRoles.moderator || role == BigBlueButtonAttendeeRoles.viewer)
+					&& !bigBlueButtonAttendeeDao.hasAttendee(identity, meeting)) {
+				bigBlueButtonAttendeeDao.createAttendee(identity, null, role, new Date(), meeting);
+			} else if((role == BigBlueButtonAttendeeRoles.guest || role == BigBlueButtonAttendeeRoles.external)
+					&& !bigBlueButtonAttendeeDao.hasAttendee(pseudo, meeting)) {
+				bigBlueButtonAttendeeDao.createAttendee(null, pseudo, role, new Date(), meeting);
+			}
+		}
 		return joinUrl;
 	}
 	
diff --git a/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonAttendeeImpl.java b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonAttendeeImpl.java
new file mode 100644
index 00000000000..86343f8698b
--- /dev/null
+++ b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonAttendeeImpl.java
@@ -0,0 +1,183 @@
+/**
+ * <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.modules.bigbluebutton.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.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.olat.basesecurity.IdentityImpl;
+import org.olat.core.id.Identity;
+import org.olat.core.id.Persistable;
+import org.olat.modules.bigbluebutton.BigBlueButtonAttendee;
+import org.olat.modules.bigbluebutton.BigBlueButtonAttendeeRoles;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+
+/**
+ * 
+ * Initial date: 6 août 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="bigbluebuttonattendee")
+@Table(name="o_bbb_attendee")
+@NamedQuery(name="hasAttendeeByIdentity", query="select attendee.key from bigbluebuttonattendee as attendee where attendee.identity.key=:identityKey and attendee.meeting.key=:meetingKey")
+@NamedQuery(name="hasAttendeeByPseudo", query="select attendee.key from bigbluebuttonattendee as attendee where attendee.meeting.key=:meetingKey and lower(attendee.pseudo)=:pseudo")
+public class BigBlueButtonAttendeeImpl implements Persistable, BigBlueButtonAttendee {
+
+	private static final long serialVersionUID = -3157941332717778067L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="lastmodified", nullable=false, insertable=true, updatable=true)
+	private Date lastModified;
+
+	@Column(name="b_role", nullable=false, insertable=true, updatable=true)
+	private String role;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="b_join_date", nullable=false, insertable=true, updatable=true)
+	private Date joinDate;
+	@Column(name="b_pseudo", nullable=true, insertable=true, updatable=false)
+	private String pseudo;
+	
+	@ManyToOne(targetEntity=IdentityImpl.class, fetch=FetchType.LAZY, optional=true)
+	@JoinColumn(name="fk_identity_id", nullable=true, insertable=true, updatable=false)
+	private Identity identity;
+	@ManyToOne(targetEntity=BigBlueButtonMeetingImpl.class, fetch=FetchType.LAZY, optional=true)
+	@JoinColumn(name="fk_meeting_id", nullable=true, insertable=true, updatable=false)
+	private BigBlueButtonMeeting meeting;
+	
+	@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 getLastModified() {
+		return lastModified;
+	}
+
+	@Override
+	public void setLastModified(Date lastModified) {
+		this.lastModified = lastModified;
+	}
+
+	public String getRole() {
+		return role;
+	}
+
+	public void setRole(String role) {
+		this.role = role;
+	}
+
+	@Override
+	public BigBlueButtonAttendeeRoles getRolesEnum() {
+		return BigBlueButtonAttendeeRoles.valueSecure(getRole());
+	}
+
+	@Override
+	public Date getJoinDate() {
+		return joinDate;
+	}
+
+	public void setJoinDate(Date joinDate) {
+		this.joinDate = joinDate;
+	}
+
+	@Override
+	public String getPseudo() {
+		return pseudo;
+	}
+
+	public void setPseudo(String pseudo) {
+		this.pseudo = pseudo;
+	}
+
+	@Override
+	public Identity getIdentity() {
+		return identity;
+	}
+
+	public void setIdentity(Identity identity) {
+		this.identity = identity;
+	}
+
+	@Override
+	public BigBlueButtonMeeting getMeeting() {
+		return meeting;
+	}
+
+	public void setMeeting(BigBlueButtonMeeting meeting) {
+		this.meeting = meeting;
+	}
+
+	@Override
+	public int hashCode() {
+		return getKey() == null ? 964210765 : getKey().hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(obj == this) {
+			return true;
+		}
+		if(obj instanceof BigBlueButtonAttendeeImpl) {
+			BigBlueButtonAttendeeImpl attendee = (BigBlueButtonAttendeeImpl)obj;
+			return getKey() != null && getKey().equals(attendee.getKey());
+		}
+		return false;
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonGuestJoinController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonGuestJoinController.java
index 8a69432f499..a005cf6b283 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonGuestJoinController.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonGuestJoinController.java
@@ -53,6 +53,7 @@ import org.olat.course.run.userview.AccessibleFilter;
 import org.olat.course.run.userview.CourseTreeNode;
 import org.olat.course.run.userview.UserCourseEnvironmentImpl;
 import org.olat.group.BusinessGroupService;
+import org.olat.modules.bigbluebutton.BigBlueButtonAttendeeRoles;
 import org.olat.modules.bigbluebutton.BigBlueButtonManager;
 import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
 import org.olat.modules.bigbluebutton.BigBlueButtonModule;
@@ -293,7 +294,15 @@ public class BigBlueButtonGuestJoinController extends FormBasicController implem
 		
 		BigBlueButtonErrors errors = new BigBlueButtonErrors();
 		String pseudo = nameEl.getValue();
-		String url = bigBlueButtonManager.join(meeting, null, pseudo, false, true, null, errors);
+
+		BigBlueButtonAttendeeRoles role;
+		UserSession usess = ureq.getUserSession();
+		if(getIdentity() == null || usess.getRoles() == null) {
+			role = BigBlueButtonAttendeeRoles.external;
+		} else {
+			role = BigBlueButtonAttendeeRoles.guest;
+		}
+		String url = bigBlueButtonManager.join(meeting, null, pseudo, role, null, errors);
 		MediaResource resource = new RedirectMediaResource(url);
 		ureq.getDispatchResult().setResultingMediaResource(resource);
 	}
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingController.java
index f501152b677..26228d3d1c4 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingController.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingController.java
@@ -55,6 +55,7 @@ import org.olat.core.util.UserSession;
 import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.core.util.event.GenericEventListener;
 import org.olat.core.util.resource.OresHelper;
+import org.olat.modules.bigbluebutton.BigBlueButtonAttendeeRoles;
 import org.olat.modules.bigbluebutton.BigBlueButtonDispatcher;
 import org.olat.modules.bigbluebutton.BigBlueButtonManager;
 import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
@@ -335,12 +336,14 @@ public class BigBlueButtonMeetingController extends FormBasicController implemen
 		String meetingUrl = null;
 		BigBlueButtonErrors errors = new BigBlueButtonErrors();
 		if(moderator || administrator) {
-			meetingUrl = bigBlueButtonManager.join(meeting, getIdentity(), null, (administrator || moderator), false, null, errors);
+			meetingUrl = bigBlueButtonManager.join(meeting, getIdentity(), null, BigBlueButtonAttendeeRoles.moderator, null, errors);
 			delayEvent(new BigBlueButtonEvent(meeting.getKey(), getIdentity().getKey()));
 		} else if(!moderatorStartMeeting) {
-			meetingUrl = bigBlueButtonManager.join(meeting, getIdentity(), null, false, guest, null, errors);
+			BigBlueButtonAttendeeRoles role = guest ? BigBlueButtonAttendeeRoles.guest : BigBlueButtonAttendeeRoles.external;
+			meetingUrl = bigBlueButtonManager.join(meeting, getIdentity(), null, role, null, errors);
 		} else if(bigBlueButtonManager.isMeetingRunning(meeting)) {
-			meetingUrl = bigBlueButtonManager.join(meeting, getIdentity(), null, false, guest, Boolean.TRUE, errors);
+			BigBlueButtonAttendeeRoles role = guest ? BigBlueButtonAttendeeRoles.guest : BigBlueButtonAttendeeRoles.external;
+			meetingUrl = bigBlueButtonManager.join(meeting, getIdentity(), null, role, Boolean.TRUE, errors);
 		}
 		redirectTo(ureq, meetingUrl, errors);
 	}
diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml
index 7ec9cfde06e..216c9ee87ef 100644
--- a/src/main/resources/META-INF/persistence.xml
+++ b/src/main/resources/META-INF/persistence.xml
@@ -171,6 +171,7 @@
 		<class>org.olat.modules.bigbluebutton.model.BigBlueButtonMeetingImpl</class>
 		<class>org.olat.modules.bigbluebutton.model.BigBlueButtonMeetingTemplateImpl</class>
 		<class>org.olat.modules.bigbluebutton.model.BigBlueButtonServerImpl</class>
+		<class>org.olat.modules.bigbluebutton.model.BigBlueButtonAttendeeImpl</class>
 		<class>org.olat.modules.curriculum.model.CurriculumImpl</class>
 		<class>org.olat.modules.curriculum.model.CurriculumElementImpl</class>
 		<class>org.olat.modules.curriculum.model.CurriculumElementTypeImpl</class>
diff --git a/src/main/resources/database/mysql/alter_15_1_x_to_15_2_0.sql b/src/main/resources/database/mysql/alter_15_1_x_to_15_2_0.sql
index 35cf3d61646..7542ec9305a 100644
--- a/src/main/resources/database/mysql/alter_15_1_x_to_15_2_0.sql
+++ b/src/main/resources/database/mysql/alter_15_1_x_to_15_2_0.sql
@@ -1 +1,21 @@
 alter table o_user add column u_nickname varchar(255);
+
+
+-- BigBlueButton
+create table o_bbb_attendee (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   b_role varchar(32),
+   b_join_date datetime,
+   b_pseudo varchar(255),
+   fk_identity_id bigint,
+   fk_meeting_id bigint not null,
+   primary key (id)
+);
+
+alter table o_bbb_attendee ENGINE = InnoDB;
+
+alter table o_bbb_attendee add constraint bbb_attend_ident_idx foreign key (fk_identity_id) references o_bs_identity (id);
+alter table o_bbb_attendee add constraint bbb_attend_meet_idx foreign key (fk_meeting_id) references o_bbb_meeting (id);
+
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index bd31b8054d8..d0d6a916ffc 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -1227,6 +1227,18 @@ create table o_bbb_meeting (
    primary key (id)
 );
 
+create table o_bbb_attendee (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   b_role varchar(32),
+   b_join_date datetime,
+   b_pseudo varchar(255),
+   fk_identity_id bigint,
+   fk_meeting_id bigint not null,
+   primary key (id)
+);
+
 
 -- assessment tables
 -- efficiency statments
@@ -3334,6 +3346,7 @@ alter table o_aconnect_user ENGINE = InnoDB;
 alter table o_bbb_template ENGINE = InnoDB;
 alter table o_bbb_meeting ENGINE = InnoDB;
 alter table o_bbb_server ENGINE = InnoDB;
+alter table o_bbb_attendee ENGINE = InnoDB;
 alter table o_im_message ENGINE = InnoDB;
 alter table o_im_notification ENGINE = InnoDB;
 alter table o_im_roster_entry ENGINE = InnoDB;
@@ -3736,6 +3749,9 @@ alter table o_bbb_meeting add constraint bbb_meet_template_idx foreign key (fk_t
 
 alter table o_bbb_meeting add constraint bbb_meet_serv_idx foreign key (fk_server_id) references o_bbb_server (id);
 
+alter table o_bbb_attendee add constraint bbb_attend_ident_idx foreign key (fk_identity_id) references o_bs_identity (id);
+alter table o_bbb_attendee add constraint bbb_attend_meet_idx foreign key (fk_meeting_id) references o_bbb_meeting (id);
+
 -- tag
 alter table o_tag add constraint FK6491FCA5A4FA5DC foreign key (fk_author_id) references o_bs_identity (id);
 
diff --git a/src/main/resources/database/oracle/alter_15_1_x_to_15_2_0.sql b/src/main/resources/database/oracle/alter_15_1_x_to_15_2_0.sql
index 535b7cbb16d..b6c8539c07a 100644
--- a/src/main/resources/database/oracle/alter_15_1_x_to_15_2_0.sql
+++ b/src/main/resources/database/oracle/alter_15_1_x_to_15_2_0.sql
@@ -1 +1,20 @@
 alter table o_user add u_nickname varchar2(255 char);
+
+
+-- BigBlueButton
+create table o_bbb_attendee (
+   id number(20) generated always as identity,
+   creationdate date not null,
+   lastmodified date not null,
+   b_role varchar2(32),
+   b_join_date date,
+   b_pseudo varchar2(255),
+   fk_identity_id number(20),
+   fk_meeting_id number(20) not null,
+   primary key (id)
+);
+
+alter table o_bbb_attendee add constraint bbb_attend_ident_idx foreign key (fk_identity_id) references o_bs_identity (id);
+create index idx_bbb_attend_ident_idx on o_bbb_attendee(fk_identity_id);
+alter table o_bbb_attendee add constraint bbb_attend_meet_idx foreign key (fk_meeting_id) references o_bbb_meeting (id);
+create index idx_bbb_attend_meet_idx on o_bbb_attendee(fk_meeting_id);
diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql
index c8a9bb557aa..1e912a28f75 100644
--- a/src/main/resources/database/oracle/setupDatabase.sql
+++ b/src/main/resources/database/oracle/setupDatabase.sql
@@ -1291,6 +1291,18 @@ create table o_bbb_meeting (
    primary key (id)
 );
 
+create table o_bbb_attendee (
+   id number(20) generated always as identity,
+   creationdate date not null,
+   lastmodified date not null,
+   b_role varchar2(32),
+   b_join_date date,
+   b_pseudo varchar2(255),
+   fk_identity_id number(20),
+   fk_meeting_id number(20) not null,
+   primary key (id)
+);
+
 
 create table o_as_eff_statement (
    id number(20) not null,
@@ -3752,7 +3764,7 @@ create index idx_aconnect_meet_grp_idx on o_aconnect_meeting(fk_group_id);
 alter table o_aconnect_user add constraint aconn_ident_idx foreign key (fk_identity_id) references o_bs_identity (id);
 create index idx_aconn_ident_idx on o_aconnect_user (fk_identity_id);
 
--- Bigbluebutton
+-- BigBlueButton
 alter table o_bbb_meeting add constraint bbb_meet_entry_idx foreign key (fk_entry_id) references o_repositoryentry (repositoryentry_id);
 create index idx_bbb_meet_entry_idx on o_bbb_meeting(fk_entry_id);
 alter table o_bbb_meeting add constraint bbb_meet_grp_idx foreign key (fk_group_id) references o_gp_business (group_id);
@@ -3762,6 +3774,11 @@ create index idx_bbb_meet_template_idx on o_bbb_meeting(fk_template_id);
 alter table o_bbb_meeting add constraint bbb_meet_serv_idx foreign key (fk_server_id) references o_bbb_server (id);
 create index idx_bbb_meet_serv_idx on o_bbb_meeting(fk_server_id);
 
+alter table o_bbb_attendee add constraint bbb_attend_ident_idx foreign key (fk_identity_id) references o_bs_identity (id);
+create index idx_bbb_attend_ident_idx on o_bbb_attendee(fk_identity_id);
+alter table o_bbb_attendee add constraint bbb_attend_meet_idx foreign key (fk_meeting_id) references o_bbb_meeting (id);
+create index idx_bbb_attend_meet_idx on o_bbb_attendee(fk_meeting_id);
+
 -- tag
 alter table o_tag add constraint FK6491FCA5A4FA5DC foreign key (fk_author_id) references o_bs_identity (id);
 create index idx_tag_to_auth_idx on o_tag (fk_author_id);
diff --git a/src/main/resources/database/postgresql/alter_15_1_x_to_15_2_0.sql b/src/main/resources/database/postgresql/alter_15_1_x_to_15_2_0.sql
index 35cf3d61646..479b4253d44 100644
--- a/src/main/resources/database/postgresql/alter_15_1_x_to_15_2_0.sql
+++ b/src/main/resources/database/postgresql/alter_15_1_x_to_15_2_0.sql
@@ -1 +1,21 @@
 alter table o_user add column u_nickname varchar(255);
+
+
+-- BigBlueButton
+create table o_bbb_attendee (
+   id bigserial,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   b_role varchar(32),
+   b_join_date timestamp,
+   b_pseudo varchar(255),
+   fk_identity_id int8,
+   fk_meeting_id int8 not null,
+   primary key (id)
+);
+
+alter table o_bbb_attendee add constraint bbb_attend_ident_idx foreign key (fk_identity_id) references o_bs_identity (id);
+create index idx_bbb_attend_ident_idx on o_bbb_attendee(fk_identity_id);
+alter table o_bbb_attendee add constraint bbb_attend_meet_idx foreign key (fk_meeting_id) references o_bbb_meeting (id);
+create index idx_bbb_attend_meet_idx on o_bbb_attendee(fk_meeting_id);
+
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index 4ed6159cc72..9cbff2da4cd 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -1249,6 +1249,18 @@ create table o_bbb_meeting (
    primary key (id)
 );
 
+create table o_bbb_attendee (
+   id bigserial,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   b_role varchar(32),
+   b_join_date timestamp,
+   b_pseudo varchar(255),
+   fk_identity_id int8,
+   fk_meeting_id int8 not null,
+   primary key (id)
+);
+
 -- efficiency statments
 create table o_as_eff_statement (
    id int8 not null,
@@ -3651,6 +3663,11 @@ create index idx_bbb_meet_template_idx on o_bbb_meeting(fk_template_id);
 alter table o_bbb_meeting add constraint bbb_meet_serv_idx foreign key (fk_server_id) references o_bbb_server (id);
 create index idx_bbb_meet_serv_idx on o_bbb_meeting(fk_server_id);
 
+alter table o_bbb_attendee add constraint bbb_attend_ident_idx foreign key (fk_identity_id) references o_bs_identity (id);
+create index idx_bbb_attend_ident_idx on o_bbb_attendee(fk_identity_id);
+alter table o_bbb_attendee add constraint bbb_attend_meet_idx foreign key (fk_meeting_id) references o_bbb_meeting (id);
+create index idx_bbb_attend_meet_idx on o_bbb_attendee(fk_meeting_id);
+
 -- tag
 alter table o_tag add constraint FK6491FCA5A4FA5DC foreign key (fk_author_id) references o_bs_identity (id);
 create index idx_tag_to_auth_idx on o_tag (fk_author_id);
diff --git a/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonAttendeeDAOTest.java b/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonAttendeeDAOTest.java
new file mode 100644
index 00000000000..8ba09e3dbbb
--- /dev/null
+++ b/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonAttendeeDAOTest.java
@@ -0,0 +1,142 @@
+/**
+ * <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.modules.bigbluebutton.manager;
+
+import java.util.Date;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.Identity;
+import org.olat.group.BusinessGroup;
+import org.olat.group.manager.BusinessGroupDAO;
+import org.olat.modules.bigbluebutton.BigBlueButtonAttendee;
+import org.olat.modules.bigbluebutton.BigBlueButtonAttendeeRoles;
+import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
+import org.olat.test.JunitTestHelper;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 6 août 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class BigBlueButtonAttendeeDAOTest extends OlatTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private BusinessGroupDAO businessGroupDao;
+	@Autowired
+	private BigBlueButtonMeetingDAO bigBlueButtonMeetingDao;
+	@Autowired
+	private BigBlueButtonAttendeeDAO bigBlueButtonAttendeeDao;
+	
+	
+	@Test
+	public void createAttendee() {
+		Identity id = JunitTestHelper.createAndPersistIdentityAsRndUser("bbb-attendee-1");
+		BusinessGroup group = businessGroupDao.createAndPersist(null, "BBB Attendees 1", "bbb-desc", -1, -1, false, false, false, false, false);
+		BigBlueButtonMeeting meeting = bigBlueButtonMeetingDao.createAndPersistMeeting("Attend - 1", null, null, group);
+		dbInstance.commit();
+		
+		BigBlueButtonAttendee attendee = bigBlueButtonAttendeeDao
+				.createAttendee(id, null, BigBlueButtonAttendeeRoles.moderator, new Date(), meeting);
+		dbInstance.commitAndCloseSession();
+		
+		Assert.assertNotNull(attendee);
+		Assert.assertNotNull(attendee.getCreationDate());
+		Assert.assertNotNull(attendee.getLastModified());
+		Assert.assertNotNull(attendee.getJoinDate());
+		Assert.assertEquals(meeting, attendee.getMeeting());
+		Assert.assertEquals(id, attendee.getIdentity());
+		Assert.assertEquals(BigBlueButtonAttendeeRoles.moderator, attendee.getRolesEnum());
+	}
+	
+	@Test
+	public void createAttendee_guest() {
+		BusinessGroup group = businessGroupDao.createAndPersist(null, "BBB Attendees 1", "bbb-desc", -1, -1, false, false, false, false, false);
+		BigBlueButtonMeeting meeting = bigBlueButtonMeetingDao.createAndPersistMeeting("Attend - 1", null, null, group);
+		dbInstance.commit();
+		
+		BigBlueButtonAttendee attendee = bigBlueButtonAttendeeDao
+				.createAttendee(null, "Ruby", BigBlueButtonAttendeeRoles.guest, new Date(), meeting);
+		dbInstance.commitAndCloseSession();
+		
+		Assert.assertNotNull(attendee);
+		Assert.assertNotNull(attendee.getCreationDate());
+		Assert.assertNotNull(attendee.getLastModified());
+		Assert.assertNotNull(attendee.getJoinDate());
+		Assert.assertEquals("Ruby", attendee.getPseudo());
+		Assert.assertNull(attendee.getIdentity());
+		Assert.assertEquals(BigBlueButtonAttendeeRoles.guest, attendee.getRolesEnum());
+	}
+	
+	@Test
+	public void hasAttendee_identified() {
+		Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("bbb-attendee-2");
+		Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("bbb-attendee-3");
+		BusinessGroup group = businessGroupDao.createAndPersist(null, "BBB Attendees 2", "bbb-desc", -1, -1, false, false, false, false, false);
+		BigBlueButtonMeeting meeting = bigBlueButtonMeetingDao.createAndPersistMeeting("Attend - 1", null, null, group);
+		BigBlueButtonAttendee attendee1 = bigBlueButtonAttendeeDao
+				.createAttendee(id1, null, BigBlueButtonAttendeeRoles.moderator, new Date(), meeting);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(attendee1);
+		
+		boolean present1 = bigBlueButtonAttendeeDao.hasAttendee(id1, meeting);
+		Assert.assertTrue(present1);
+		boolean present2 = bigBlueButtonAttendeeDao.hasAttendee(id2, meeting);
+		Assert.assertFalse(present2);
+	}
+	
+	@Test
+	public void hasAttendee_guest() {
+		BusinessGroup group = businessGroupDao.createAndPersist(null, "BBB Attendees 2", "bbb-desc", -1, -1, false, false, false, false, false);
+		BigBlueButtonMeeting meeting = bigBlueButtonMeetingDao.createAndPersistMeeting("Attend - 1", null, null, group);
+		BigBlueButtonAttendee attendee1 = bigBlueButtonAttendeeDao
+				.createAttendee(null, "Jeremey", BigBlueButtonAttendeeRoles.guest, new Date(), meeting);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(attendee1);
+		
+		boolean present1 = bigBlueButtonAttendeeDao.hasAttendee("Jeremey", meeting);
+		Assert.assertTrue(present1);
+		boolean present2 = bigBlueButtonAttendeeDao.hasAttendee("Albert", meeting);
+		Assert.assertFalse(present2);
+	}
+	
+	@Test
+	public void getAttendee() {
+		Identity id = JunitTestHelper.createAndPersistIdentityAsRndUser("bbb-attendee-4");
+		BusinessGroup group = businessGroupDao.createAndPersist(null, "BBB Attendees 2", "bbb-desc", -1, -1, false, false, false, false, false);
+		BigBlueButtonMeeting meeting = bigBlueButtonMeetingDao.createAndPersistMeeting("Attend - 1", null, null, group);
+		BigBlueButtonAttendee attendee = bigBlueButtonAttendeeDao
+				.createAttendee(id, null, BigBlueButtonAttendeeRoles.moderator, new Date(), meeting);
+		dbInstance.commitAndCloseSession();
+			
+		BigBlueButtonAttendee reloadedAttendee = bigBlueButtonAttendeeDao.getAttendee(id, meeting);
+		Assert.assertNotNull(reloadedAttendee);
+		Assert.assertEquals(attendee, reloadedAttendee);
+		Assert.assertEquals(id, reloadedAttendee.getIdentity());
+		Assert.assertEquals(meeting, reloadedAttendee.getMeeting());
+	}
+
+}
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index e080b17f85a..84035c257a8 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -215,6 +215,7 @@ import org.junit.runners.Suite;
 	org.olat.modules.appointments.manager.TopicToGroupDAOTest.class,
 	org.olat.modules.bigbluebutton.manager.BigBlueButtonServerDAOTest.class,
 	org.olat.modules.bigbluebutton.manager.BigBlueButtonMeetingDAOTest.class,
+	org.olat.modules.bigbluebutton.manager.BigBlueButtonAttendeeDAOTest.class,
 	org.olat.modules.bigbluebutton.manager.BigBlueButtonMeetingTemplateDAOTest.class,
 	org.olat.modules.bigbluebutton.manager.BigBlueButtonUriBuilderTest.class,
 	org.olat.modules.iq.IQManagerTest.class,
-- 
GitLab