diff --git a/src/main/java/org/olat/core/gui/media/ServletUtil.java b/src/main/java/org/olat/core/gui/media/ServletUtil.java index 0b5dc734cb44c833f5e26d5c790b11625df6d96a..f7bc631ee1b12c44ecf3a4c3b12bd507edeb266b 100644 --- a/src/main/java/org/olat/core/gui/media/ServletUtil.java +++ b/src/main/java/org/olat/core/gui/media/ServletUtil.java @@ -230,17 +230,23 @@ public class ServletUtil { } } catch (IOException e) { FileUtils.closeSafely(out); + handleIOException("client browser probably abort when serving media resource", e); + } finally { + IOUtils.closeQuietly(bis); + IOUtils.closeQuietly(in); + } + } + + public static final void handleIOException(String msg, Exception e) { + try { String className = e.getClass().getSimpleName(); if("ClientAbortException".equals(className)) { - if(log.isDebugEnabled()) {//video generate a lot of these errors - log.warn("client browser probably abort when serving media resource", e); - } + log.debug("client browser probably abort during operaation", e); } else { - log.error("client browser probably abort when serving media resource", e); + log.error(msg, e); } - } finally { - IOUtils.closeQuietly(bis); - IOUtils.closeQuietly(in); + } catch (Exception e1) { + log.error("", e1); } } diff --git a/src/main/java/org/olat/core/servlets/StaticServlet.java b/src/main/java/org/olat/core/servlets/StaticServlet.java index e18edb5439ae17a473bbcad19c62650749039c49..43b77cdba4a6a288c24c05907ad3ecf8872c75a5 100644 --- a/src/main/java/org/olat/core/servlets/StaticServlet.java +++ b/src/main/java/org/olat/core/servlets/StaticServlet.java @@ -205,6 +205,8 @@ public class StaticServlet extends HttpServlet { try(InputStream in = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(in, FileUtils.BSIZE)) { FileUtils.cpio(bis, response.getOutputStream(), "static"); + } catch(IOException e) { + ServletUtil.handleIOException("", e); } catch(Exception ex) { log.error("", ex); } diff --git a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java index 429f3fae1eb07117e9c846d8c0ebc281f43ce0fc..8e18ffd750a888d19168138be3ae27d6bc5ca560 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java +++ b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java @@ -37,6 +37,17 @@ import org.olat.repository.RepositoryEntryRef; */ public interface BigBlueButtonManager { + public BigBlueButtonServer createServer(String url, String recordingUrl, String sharedSecret); + + public BigBlueButtonServer updateServer(BigBlueButtonServer server); + + public boolean hasServer(String url); + + public List<BigBlueButtonServer> getServers(); + + public void deleteServer(BigBlueButtonServer server, BigBlueButtonErrors errors); + + /** * Create and persist a meeting in OpenOlat. The method will generate * an unique meeting identifier and passwords for attendees and moderators. diff --git a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeeting.java b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeeting.java index a5cf5a36af378b54dfe370062cd73749bac82332..0534275e6e6d9a9e68dff632d36c4709a95f18dc 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeeting.java +++ b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeeting.java @@ -87,4 +87,6 @@ public interface BigBlueButtonMeeting extends ModifiedInfo, CreateInfo { public RepositoryEntry getEntry(); public String getSubIdent(); + + public BigBlueButtonServer getServer(); } diff --git a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonServer.java b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonServer.java new file mode 100644 index 0000000000000000000000000000000000000000..562d600f9b452e9189926962dfdfa52669540905 --- /dev/null +++ b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonServer.java @@ -0,0 +1,59 @@ +/** + * <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 org.olat.core.id.CreateInfo; +import org.olat.core.id.ModifiedInfo; + +/** + * + * Initial date: 7 avr. 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public interface BigBlueButtonServer extends ModifiedInfo, CreateInfo { + + public Long getKey(); + + public String getName(); + + public void setName(String name); + + public String getUrl(); + + public void setUrl(String url); + + public String getSharedSecret(); + + public void setSharedSecret(String secret); + + public String getRecordingUrl(); + + public void setRecordingUrl(String recordingUrl); + + public boolean isEnabled(); + + public void setEnabled(boolean enabled); + + public Double getCapacityFactory(); + + public void setCapacityFactory(Double capacityFactory); + +} 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 29baa187fddec273aabe505def9c824a15713978..bfda0743bb68bdd8106563c30aa1df008c965b67 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java +++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java @@ -24,6 +24,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; @@ -35,6 +38,7 @@ import org.olat.commons.calendar.CalendarManager; import org.olat.commons.calendar.model.Kalendar; import org.olat.commons.calendar.model.KalendarEvent; import org.olat.commons.calendar.ui.components.KalendarRenderWrapper; +import org.olat.core.commons.persistence.DB; import org.olat.core.id.Identity; import org.olat.core.id.Roles; import org.olat.core.id.User; @@ -50,13 +54,15 @@ import org.olat.group.BusinessGroupService; import org.olat.modules.bigbluebutton.BigBlueButtonManager; import org.olat.modules.bigbluebutton.BigBlueButtonMeeting; import org.olat.modules.bigbluebutton.BigBlueButtonMeetingTemplate; -import org.olat.modules.bigbluebutton.BigBlueButtonModule; import org.olat.modules.bigbluebutton.BigBlueButtonRecording; +import org.olat.modules.bigbluebutton.BigBlueButtonServer; import org.olat.modules.bigbluebutton.BigBlueButtonTemplatePermissions; import org.olat.modules.bigbluebutton.GuestPolicyEnum; import org.olat.modules.bigbluebutton.model.BigBlueButtonError; import org.olat.modules.bigbluebutton.model.BigBlueButtonErrorCodes; import org.olat.modules.bigbluebutton.model.BigBlueButtonErrors; +import org.olat.modules.bigbluebutton.model.BigBlueButtonMeetingImpl; +import org.olat.modules.bigbluebutton.model.BigBlueButtonMeetingInfos; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryRef; import org.olat.repository.RepositoryManager; @@ -78,6 +84,8 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager, Initializ private static final Logger log = Tracing.createLoggerFor(BigBlueButtonManagerImpl.class); + @Autowired + private DB dbInstance; @Autowired private CalendarManager calendarManager; @Autowired @@ -85,10 +93,10 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager, Initializ @Autowired private RepositoryEntryDAO repositoryEntryDao; @Autowired - private BigBlueButtonModule bigBlueButtonModule; - @Autowired private BusinessGroupService businessGroupService; @Autowired + private BigBlueButtonServerDAO bigBlueButtonServerDao; + @Autowired private BigBlueButtonMeetingDAO bigBlueButtonMeetingDao; @Autowired private BigBlueButtonMeetingTemplateDAO bigBlueButtonMeetingTemplateDao; @@ -202,6 +210,36 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager, Initializ bigBlueButtonMeetingTemplateDao.updateTemplate(template); } + @Override + public BigBlueButtonServer createServer(String url, String recordingUrl, String sharedSecret) { + return bigBlueButtonServerDao.createServer(url, recordingUrl, sharedSecret); + } + + @Override + public BigBlueButtonServer updateServer(BigBlueButtonServer server) { + return bigBlueButtonServerDao.updateServer(server); + } + + @Override + public List<BigBlueButtonServer> getServers() { + return bigBlueButtonServerDao.getServers(); + } + + @Override + public void deleteServer(BigBlueButtonServer server, BigBlueButtonErrors errors) { + List<BigBlueButtonMeeting> meetings = bigBlueButtonMeetingDao.getMeetings(server); + for(BigBlueButtonMeeting meeting:meetings) { + deleteMeeting(meeting, errors); + } + bigBlueButtonServerDao.deleteServer(server); + } + + @Override + public boolean hasServer(String url) { + List<BigBlueButtonServer> servers = this.getServers(); + return servers.stream().anyMatch(server -> server.getUrl().startsWith(url) || url.startsWith(server.getUrl())); + } + @Override public BigBlueButtonMeeting createAndPersistMeeting(String name, RepositoryEntry entry, String subIdent, BusinessGroup businessGroup) { return bigBlueButtonMeetingDao.createAndPersistMeeting(name, entry, subIdent, businessGroup); @@ -295,6 +333,77 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager, Initializ return false; } + public BigBlueButtonServer getAvailableServer() { + List<BigBlueButtonServer> servers = getServers(); + List<BigBlueButtonServer> availableServers = servers.stream() + .filter(BigBlueButtonServer::isEnabled) + .collect(Collectors.toList()); + if(availableServers.isEmpty()) { + return null; + } else if(availableServers.size() == 1) {//TODO + return availableServers.get(0); + } + return getBigBlueButtonServer(servers); + } + + private BigBlueButtonServer getBigBlueButtonServer(List<BigBlueButtonServer> servers) { + CountDownLatch serverLatch = new CountDownLatch(servers.size()); + + List<MeetingInfosThread> threads = new ArrayList<>(); + for(BigBlueButtonServer server:servers) { + MeetingInfosThread thread = new MeetingInfosThread(server, serverLatch); + threads.add(thread); + thread.start(); + } + + try { + serverLatch.await(15, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.error("", e); + } + + List<ServerLoad> serversLoad = threads.stream() + .filter(MeetingInfosThread::isExecuted) + .filter(thread -> !thread.hasErrors()) + .map(thread -> calculateLoad(thread.getServer(), thread.getMeetingsInfos())) + .collect(Collectors.toList()); + + if(serversLoad.isEmpty()) { + return null; + } + Collections.sort(serversLoad); + return serversLoad.get(0).getServer(); + } + + private ServerLoad calculateLoad(BigBlueButtonServer server, List<BigBlueButtonMeetingInfos> meetingsInfos) { + double load = 0.0d; + for(BigBlueButtonMeetingInfos meetingInfos:meetingsInfos) { + load += meetingInfos.getListenerCount() * 1.0d; + load += meetingInfos.getVideoCount() * 3.0d; + load += meetingInfos.getVoiceParticipantCount() * 2.0d; + } + if(load > 0.0d + && server.getCapacityFactory() != null + && server.getCapacityFactory().doubleValue() > 1.0) { + load = load / server.getCapacityFactory().doubleValue(); + } + return new ServerLoad(server, load); + } + + private List<BigBlueButtonMeetingInfos> getMeetingInfos(BigBlueButtonServer server, BigBlueButtonErrors errors) { + BigBlueButtonUriBuilder uriBuilder = getUriBuilder(server); + uriBuilder + .operation("getMeetings"); + + Document doc = sendRequest(uriBuilder, errors); + BigBlueButtonUtils.print(doc); + if(BigBlueButtonUtils.checkSuccess(doc, errors)) { + BigBlueButtonUtils.print(doc); + return BigBlueButtonUtils.getMeetings(doc); + } + return new ArrayList<>(); + } + private void deleteRecordings(BigBlueButtonMeeting meeting, BigBlueButtonErrors errors) { StringBuilder sb = new StringBuilder(); @@ -310,12 +419,12 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager, Initializ } if(sb.length() > 0) { - deleteRecording(sb.toString(), errors); + deleteRecording(sb.toString(), meeting.getServer(), errors); } } - private void deleteRecording(String recordId, BigBlueButtonErrors errors) { - BigBlueButtonUriBuilder uriBuilder = getUriBuilder(); + private void deleteRecording(String recordId, BigBlueButtonServer server, BigBlueButtonErrors errors) { + BigBlueButtonUriBuilder uriBuilder = getUriBuilder(server); uriBuilder .operation("deleteRecordings") .parameter("recordID", recordId); @@ -422,7 +531,12 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager, Initializ @Override public boolean isMeetingRunning(BigBlueButtonMeeting meeting) { - BigBlueButtonUriBuilder uriBuilder = getUriBuilder(); + BigBlueButtonServer server = meeting.getServer(); + if(server == null) { + return false; + } + + BigBlueButtonUriBuilder uriBuilder = getUriBuilder(server); uriBuilder .operation("isMeetingRunning") .parameter("meetingID", meeting.getMeetingId()); @@ -438,14 +552,21 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager, Initializ @Override public String join(BigBlueButtonMeeting meeting, Identity identity, boolean moderator, boolean guest, Boolean isRunning, BigBlueButtonErrors errors) { + this.getAvailableServer(); + String joinUrl = null; - if((isRunning != null && isRunning.booleanValue()) || createBigBlueButtonMeeting(meeting, errors)) { - joinUrl = buildJoinUrl(meeting, identity, moderator, guest); + if(isRunning != null && isRunning.booleanValue() && meeting.getServer() != null) { + joinUrl = buildJoinUrl(meeting, meeting.getServer(), identity, moderator, guest); + } else { + meeting = getMeetingWithServer(meeting); + if(createBigBlueButtonMeeting(meeting, errors)) { + joinUrl = buildJoinUrl(meeting, meeting.getServer(), identity, moderator, guest); + } } return joinUrl; } - private String buildJoinUrl(BigBlueButtonMeeting meeting, Identity identity, boolean moderator, boolean guest) { + private String buildJoinUrl(BigBlueButtonMeeting meeting, BigBlueButtonServer server, Identity identity, boolean moderator, boolean guest) { String password = moderator ? meeting.getModeratorPassword() : meeting.getAttendeePassword(); String userId = null; @@ -453,7 +574,7 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager, Initializ userId = WebappHelper.getInstanceId() + "-" + identity.getKey(); } - BigBlueButtonUriBuilder uriBuilder = getUriBuilder(); + BigBlueButtonUriBuilder uriBuilder = getUriBuilder(server); return uriBuilder .operation("join") .parameter("meetingID", meeting.getMeetingId()) @@ -491,11 +612,35 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager, Initializ } return BusinessControlFactory.getInstance().getURLFromBusinessPathString(businessPath); } + + private BigBlueButtonMeeting getMeetingWithServer(BigBlueButtonMeeting meeting) { + meeting = bigBlueButtonMeetingDao.loadByKey(meeting.getKey()); + if(meeting.getServer() != null) { + return meeting; + } + + BigBlueButtonServer availableServer = getAvailableServer(); + if(availableServer == null) { + return meeting; + } + + BigBlueButtonMeeting lockedMeeting = bigBlueButtonMeetingDao.loadForUpdate(meeting); + BigBlueButtonServer currentServer = lockedMeeting.getServer(); + if(currentServer == null) { + ((BigBlueButtonMeetingImpl)lockedMeeting).setServer(availableServer); + meeting = bigBlueButtonMeetingDao.updateMeeting(lockedMeeting); + meeting.getServer().getUrl();// ensure server is loaded + } + dbInstance.commit(); + + return meeting; + } private boolean createBigBlueButtonMeeting(BigBlueButtonMeeting meeting, BigBlueButtonErrors errors) { BigBlueButtonMeetingTemplate template = meeting.getTemplate(); + BigBlueButtonServer server = meeting.getServer(); - BigBlueButtonUriBuilder uriBuilder = getUriBuilder(); + BigBlueButtonUriBuilder uriBuilder = getUriBuilder(server); uriBuilder .operation("create") .optionalParameter("name", meeting.getName()) @@ -545,7 +690,11 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager, Initializ @Override public List<BigBlueButtonRecording> getRecordings(BigBlueButtonMeeting meeting, BigBlueButtonErrors errors) { - BigBlueButtonUriBuilder uriBuilder = getUriBuilder(); + if(meeting.getServer() == null) { + return new ArrayList<>(); + } + + BigBlueButtonUriBuilder uriBuilder = getUriBuilder(meeting.getServer()); uriBuilder .operation("getRecordings") .parameter("meetingID", meeting.getMeetingId()); @@ -558,8 +707,8 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager, Initializ return Collections.emptyList(); } - public void getBigBlueButtonDefaultConfigXml() { - BigBlueButtonUriBuilder uriBuilder = getUriBuilder(); + public void getBigBlueButtonDefaultConfigXml(BigBlueButtonServer server) { + BigBlueButtonUriBuilder uriBuilder = getUriBuilder(server); uriBuilder .operation("getDefaultConfigXML"); BigBlueButtonErrors errors = new BigBlueButtonErrors(); @@ -579,8 +728,9 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager, Initializ return false; } - private BigBlueButtonUriBuilder getUriBuilder() { - return BigBlueButtonUriBuilder.fromUri(bigBlueButtonModule.getBigBlueButtonURI(), bigBlueButtonModule.getSharedSecret()); + private BigBlueButtonUriBuilder getUriBuilder(BigBlueButtonServer server) { + URI uri = URI.create(server.getUrl()); + return BigBlueButtonUriBuilder.fromUri(uri, server.getSharedSecret()); } protected Document sendRequest(BigBlueButtonUriBuilder builder, BigBlueButtonErrors errors) { @@ -597,4 +747,68 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager, Initializ return null; } } + + private static class ServerLoad implements Comparable<ServerLoad> { + + private final double load; + private final BigBlueButtonServer server; + + public ServerLoad(BigBlueButtonServer server, double load) { + this.server = server; + this.load = load; + } + + public BigBlueButtonServer getServer() { + return server; + } + + @Override + public int compareTo(ServerLoad o) { + return Double.compare(load, o.load); + } + } + + private class MeetingInfosThread extends Thread { + + private boolean executed = false; + private final CountDownLatch latch; + private final BigBlueButtonServer server; + private List<BigBlueButtonMeetingInfos> infos; + private final BigBlueButtonErrors errors = new BigBlueButtonErrors(); + + public MeetingInfosThread(BigBlueButtonServer server, CountDownLatch latch) { + super("BBB-Meetings-Infos"); + this.latch = latch; + this.server = server; + setDaemon(true); + } + + public boolean isExecuted() { + return executed; + } + + public boolean hasErrors() { + return errors.hasErrors(); + } + + public List<BigBlueButtonMeetingInfos> getMeetingsInfos() { + return infos; + } + + public BigBlueButtonServer getServer() { + return server; + } + + @Override + public void run() { + try { + infos = getMeetingInfos(server, errors); + executed = true; + } catch(Exception e) { + log.error("", e); + } finally { + latch.countDown(); + } + } + } } diff --git a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAO.java b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAO.java index 8fff348429400daf1035515b496b7b013b559273..e23bea5fcc9e053b9f5e3787e237168cc4e7cd9d 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAO.java +++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAO.java @@ -24,6 +24,7 @@ import java.util.Date; import java.util.List; import java.util.UUID; +import javax.persistence.LockModeType; import javax.persistence.TypedQuery; import org.olat.core.commons.persistence.DB; @@ -32,6 +33,7 @@ import org.olat.core.util.StringHelper; import org.olat.group.BusinessGroup; import org.olat.modules.bigbluebutton.BigBlueButtonMeeting; import org.olat.modules.bigbluebutton.BigBlueButtonMeetingTemplate; +import org.olat.modules.bigbluebutton.BigBlueButtonServer; import org.olat.modules.bigbluebutton.model.BigBlueButtonMeetingImpl; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryRef; @@ -74,6 +76,7 @@ public class BigBlueButtonMeetingDAO { .append(" left join fetch meeting.entry as entry") .append(" left join fetch meeting.businessGroup as businessGroup") .append(" left join fetch meeting.template as template") + .append(" left join fetch meeting.server as server") .append(" where meeting.key=:meetingKey"); List<BigBlueButtonMeeting> meetings = dbInstance.getCurrentEntityManager() @@ -83,6 +86,37 @@ public class BigBlueButtonMeetingDAO { return meetings == null || meetings.isEmpty() ? null : meetings.get(0); } + public BigBlueButtonMeeting loadForUpdate(BigBlueButtonMeeting meeting) { + //first remove it from caches + dbInstance.getCurrentEntityManager().detach(meeting); + + StringBuilder sb = new StringBuilder(); + sb.append("select meeting from bigbluebuttonmeeting as meeting") + .append(" where meeting.key=:meetingKey"); + + List<BigBlueButtonMeeting> meetings = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), BigBlueButtonMeeting.class) + .setParameter("meetingKey", meeting.getKey()) + .setLockMode(LockModeType.PESSIMISTIC_WRITE) + .getResultList(); + return meetings == null || meetings.isEmpty() ? null : meetings.get(0); + } + + public List<BigBlueButtonMeeting> getMeetings(BigBlueButtonServer server) { + QueryBuilder sb = new QueryBuilder(); + sb.append("select meeting from bigbluebuttonmeeting as meeting") + .append(" left join fetch meeting.entry as entry") + .append(" left join fetch meeting.businessGroup as businessGroup") + .append(" left join fetch meeting.template as template") + .append(" left join fetch meeting.server as server") + .append(" where meeting.server.key=:serverKey"); + + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), BigBlueButtonMeeting.class) + .setParameter("serverKey", server.getKey()) + .getResultList(); + } + public BigBlueButtonMeeting updateMeeting(BigBlueButtonMeeting meeting) { meeting.setLastModified(new Date()); updateDates((BigBlueButtonMeetingImpl)meeting, @@ -155,7 +189,8 @@ public class BigBlueButtonMeetingDAO { sb.append("select meeting from bigbluebuttonmeeting as meeting") .append(" left join fetch meeting.entry as entry") .append(" left join fetch meeting.businessGroup as businessGroup") - .append(" left join fetch meeting.template as template"); + .append(" left join fetch meeting.template as template") + .append(" left join fetch meeting.server as server"); return dbInstance.getCurrentEntityManager() .createQuery(sb.toString(), BigBlueButtonMeeting.class) .getResultList(); @@ -188,7 +223,8 @@ public class BigBlueButtonMeetingDAO { public List<BigBlueButtonMeeting> getMeetings(RepositoryEntryRef entry, String subIdent, BusinessGroup businessGroup) { QueryBuilder sb = new QueryBuilder(); sb.append("select meeting from bigbluebuttonmeeting as meeting") - .append(" left join fetch meeting.template as template"); + .append(" left join fetch meeting.template as template") + .append(" left join fetch meeting.server as server"); if(entry != null) { sb.and().append("meeting.entry.key=:entryKey"); if(StringHelper.containsNonWhitespace(subIdent)) { @@ -218,6 +254,7 @@ public class BigBlueButtonMeetingDAO { QueryBuilder sb = new QueryBuilder(); sb.append("select meeting from bigbluebuttonmeeting as meeting") .append(" left join fetch meeting.template as template") + .append(" left join fetch meeting.server as server") .append(" where meeting.entry.key=:entryKey and meeting.permanent=false") .append(" and meeting.startDate is not null and meeting.endDate is not null"); if(StringHelper.containsNonWhitespace(subIdent)) { diff --git a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonServerDAO.java b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonServerDAO.java new file mode 100644 index 0000000000000000000000000000000000000000..86a83382dd06b984814da9d2fa6bd61944e1bf5c --- /dev/null +++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonServerDAO.java @@ -0,0 +1,74 @@ +/** + * <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.core.commons.persistence.DB; +import org.olat.modules.bigbluebutton.BigBlueButtonServer; +import org.olat.modules.bigbluebutton.model.BigBlueButtonServerImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * + * Initial date: 7 avr. 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Service +public class BigBlueButtonServerDAO { + + @Autowired + private DB dbInstance; + + public BigBlueButtonServer createServer(String url, String recordingUrl, String sharedSecret) { + BigBlueButtonServerImpl server = new BigBlueButtonServerImpl(); + server.setCreationDate(new Date()); + server.setLastModified(server.getCreationDate()); + server.setUrl(url); + server.setRecordingUrl(recordingUrl); + server.setSharedSecret(sharedSecret); + server.setEnabled(true); + server.setCapacityFactory(1.0d); + dbInstance.getCurrentEntityManager().persist(server); + return server; + } + + public BigBlueButtonServer updateServer(BigBlueButtonServer server) { + ((BigBlueButtonServerImpl)server).setLastModified(new Date()); + return dbInstance.getCurrentEntityManager().merge(server); + } + + public void deleteServer(BigBlueButtonServer server) { + BigBlueButtonServer reloadedServer = dbInstance.getCurrentEntityManager() + .getReference(BigBlueButtonServerImpl.class, server.getKey()); + dbInstance.getCurrentEntityManager().remove(reloadedServer); + } + + public List<BigBlueButtonServer> getServers() { + String q = "select server from bigbluebuttonserver server"; + return dbInstance.getCurrentEntityManager() + .createQuery(q, BigBlueButtonServer.class) + .getResultList(); + } + +} diff --git a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonUtils.java b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonUtils.java index 7535be26ba21d538b6c204993b4cf72b64a3c938..9c5cb30fee6654348c4cd8a677a4b90afa414886 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonUtils.java +++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonUtils.java @@ -43,6 +43,7 @@ import org.olat.core.util.StringHelper; import org.olat.modules.bigbluebutton.BigBlueButtonRecording; import org.olat.modules.bigbluebutton.model.BigBlueButtonError; import org.olat.modules.bigbluebutton.model.BigBlueButtonErrors; +import org.olat.modules.bigbluebutton.model.BigBlueButtonMeetingInfos; import org.olat.modules.bigbluebutton.model.BigBlueButtonRecordingImpl; import org.w3c.dom.CharacterData; import org.w3c.dom.Document; @@ -127,6 +128,43 @@ public class BigBlueButtonUtils { return recordings; } + + protected static List<BigBlueButtonMeetingInfos> getMeetings(Document document) { + List<BigBlueButtonMeetingInfos> meetings = new ArrayList<>(); + NodeList meetingList = document.getElementsByTagName("meeting"); + for(int i=meetingList.getLength(); i-->0; ) { + Element meetingEl = (Element)meetingList.item(i); + String meetingId = getFirstElementValue(meetingEl, "meetingID"); + BigBlueButtonMeetingInfos meeting = new BigBlueButtonMeetingInfos(meetingId); + meetings.add(meeting); + + String videoCount = getFirstElementValue(meetingEl, "videoCount"); + meeting.setVideoCount(toLong(videoCount)); + String listenerCount = getFirstElementValue(meetingEl, "listenerCount"); + meeting.setListenerCount(toLong(listenerCount)); + String voiceParticipantCount = getFirstElementValue(meetingEl, "voiceParticipantCount"); + meeting.setVoiceParticipantCount(toLong(voiceParticipantCount)); + + String participantCount = getFirstElementValue(meetingEl, "participantCount"); + meeting.setParticipantCount(toLong(participantCount)); + String moderatorCount = getFirstElementValue(meetingEl, "moderatorCount"); + meeting.setModeratorCount(toLong(moderatorCount)); + } + return meetings; + } + + private static long toLong(String text) { + if(StringHelper.isLong(text)) { + try { + return Long.parseLong(text); + } catch (NumberFormatException e) { + log.error("Cannot parse this long: {0}", text, e); + } + + } + return 0l; + } + private static Date toDate(String val) { if(StringHelper.isLong(val)) { Long time = Long.parseLong(val); diff --git a/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingImpl.java b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingImpl.java index bf3266f32bb9c9535fda943f9a10e7dc990d4f55..146482c3d057df5a6e9a6a54c9dabe69e082b7ec 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingImpl.java +++ b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingImpl.java @@ -38,6 +38,7 @@ import org.olat.group.BusinessGroup; import org.olat.group.BusinessGroupImpl; import org.olat.modules.bigbluebutton.BigBlueButtonMeeting; import org.olat.modules.bigbluebutton.BigBlueButtonMeetingTemplate; +import org.olat.modules.bigbluebutton.BigBlueButtonServer; import org.olat.repository.RepositoryEntry; /** @@ -110,6 +111,10 @@ public class BigBlueButtonMeetingImpl implements Persistable, BigBlueButtonMeeti @ManyToOne(targetEntity=BigBlueButtonMeetingTemplateImpl.class, fetch=FetchType.LAZY, optional=true) @JoinColumn(name="fk_template_id", nullable=true, insertable=true, updatable=true) private BigBlueButtonMeetingTemplate template; + + @ManyToOne(targetEntity=BigBlueButtonServerImpl.class, fetch=FetchType.LAZY, optional=true) + @JoinColumn(name="fk_server_id", nullable=true, insertable=true, updatable=true) + private BigBlueButtonServer server; @Override public Long getKey() { @@ -301,6 +306,15 @@ public class BigBlueButtonMeetingImpl implements Persistable, BigBlueButtonMeeti this.template = template; } + @Override + public BigBlueButtonServer getServer() { + return server; + } + + public void setServer(BigBlueButtonServer server) { + this.server = server; + } + @Override public int hashCode() { return getKey() == null ? 964210765 : getKey().hashCode(); diff --git a/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingInfos.java b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingInfos.java new file mode 100644 index 0000000000000000000000000000000000000000..5f0fea5e1faa625f3d7de792f4c6da37ee3eb3ea --- /dev/null +++ b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingInfos.java @@ -0,0 +1,103 @@ +/** + * <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; + +/** + * + * Initial date: 8 avr. 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class BigBlueButtonMeetingInfos { + + private final String meetingId; + + private long videoCount; + private long listenerCount; + private long voiceParticipantCount; + + private long participantCount; + private long moderatorCount; + + public BigBlueButtonMeetingInfos(String meetingId) { + this.meetingId = meetingId; + } + + public String getMeetingId() { + return meetingId; + } + + public long getVideoCount() { + return videoCount; + } + + public void setVideoCount(long videoCount) { + this.videoCount = videoCount; + } + + public long getListenerCount() { + return listenerCount; + } + + public void setListenerCount(long listenerCount) { + this.listenerCount = listenerCount; + } + + public long getVoiceParticipantCount() { + return voiceParticipantCount; + } + + public void setVoiceParticipantCount(long voiceParticipantCount) { + this.voiceParticipantCount = voiceParticipantCount; + } + + public long getParticipantCount() { + return participantCount; + } + + public void setParticipantCount(long participantCount) { + this.participantCount = participantCount; + } + + public long getModeratorCount() { + return moderatorCount; + } + + public void setModeratorCount(long moderatorCount) { + this.moderatorCount = moderatorCount; + } + + @Override + public int hashCode() { + return meetingId == null ? 127846 : meetingId.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof BigBlueButtonMeetingInfos) { + BigBlueButtonMeetingInfos meeting = (BigBlueButtonMeetingInfos)obj; + return meetingId != null && meetingId.equals(meeting.getMeetingId()); + } + return false; + } +} diff --git a/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonServerImpl.java b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonServerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..b3911110930ac34b3c9aff2c0fc5746098be2fe8 --- /dev/null +++ b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonServerImpl.java @@ -0,0 +1,184 @@ +/** + * <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.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.olat.core.id.Persistable; +import org.olat.modules.bigbluebutton.BigBlueButtonServer; + +/** + * + * Initial date: 7 avr. 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Entity(name="bigbluebuttonserver") +@Table(name="o_bbb_server") +public class BigBlueButtonServerImpl implements Persistable, BigBlueButtonServer { + + private static final long serialVersionUID = 8664921045147695070L; + + @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_name", nullable=true, insertable=true, updatable=true) + private String name; + + @Column(name="b_url", nullable=false, insertable=true, updatable=true) + private String url; + @Column(name="b_shared_secret", nullable=false, insertable=true, updatable=true) + private String sharedSecret; + @Column(name="b_recording_url", nullable=true, insertable=true, updatable=true) + private String recordingUrl; + + @Column(name="b_capacity_factor", nullable=false, insertable=true, updatable=true) + private Double capacityFactory; + @Column(name="b_enabled", nullable=false, insertable=true, updatable=true) + private boolean enabled; + + @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; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public String getUrl() { + return url; + } + + @Override + public void setUrl(String url) { + this.url = url; + } + + @Override + public String getSharedSecret() { + return sharedSecret; + } + + @Override + public void setSharedSecret(String secret) { + this.sharedSecret = secret; + } + + @Override + public String getRecordingUrl() { + return recordingUrl; + } + + @Override + public void setRecordingUrl(String recordingUrl) { + this.recordingUrl = recordingUrl; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public Double getCapacityFactory() { + return capacityFactory; + } + + @Override + public void setCapacityFactory(Double capacityFactory) { + this.capacityFactory = capacityFactory; + } + + @Override + public int hashCode() { + return getKey() == null ? -378178 : getKey().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(obj == this) { + return true; + } + if(obj instanceof BigBlueButtonServerImpl) { + BigBlueButtonServerImpl server = (BigBlueButtonServerImpl)obj; + return getKey() != null && getKey().equals(server.getKey()); + } + return false; + } + + @Override + public boolean equalsByPersistableKey(Persistable persistable) { + return equals(persistable); + } +} diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminController.java index 0d1b224c6f876db76a2da4842c515318f099ef15..c3dd16119d4ca09bab025cbb02e11a765ff00e2f 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminController.java +++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminController.java @@ -47,6 +47,7 @@ import org.olat.core.util.resource.OresHelper; public class BigBlueButtonAdminController extends BasicController implements Activateable2 { private Link configurationLink; + //private final Link serversLink; private final Link meetingsLink; private final Link templatesLink; private final Link calendarLink; @@ -68,11 +69,13 @@ public class BigBlueButtonAdminController extends BasicController implements Act mainVC = createVelocityContainer("bbb_admin"); - segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this); + segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this); if(!configurationReadOnly) { configurationLink = LinkFactory.createLink("account.configuration", mainVC, this); segmentView.addSegment(configurationLink, true); } + //serversLink = LinkFactory.createLink("servers.title", mainVC, this); + //segmentView.addSegment(serversLink, false); templatesLink = LinkFactory.createLink("templates.title", mainVC, this); segmentView.addSegment(templatesLink, false); meetingsLink = LinkFactory.createLink("meetings.title", mainVC, this); diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminServersController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminServersController.java new file mode 100644 index 0000000000000000000000000000000000000000..b9d9fa5ccf5c792cd4a109b6e8490bf555a69c3b --- /dev/null +++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminServersController.java @@ -0,0 +1,56 @@ +/** + * <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.ui; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; + +/** + * + * Initial date: 7 avr. 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class BigBlueButtonAdminServersController extends FormBasicController { + + public BigBlueButtonAdminServersController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + // + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void formOK(UserRequest ureq) { + // + } +} diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminServersTableModel.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminServersTableModel.java new file mode 100644 index 0000000000000000000000000000000000000000..39c4538ad4e04a36b1a21779216931badbb32fe7 --- /dev/null +++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminServersTableModel.java @@ -0,0 +1,69 @@ +/** + * <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.ui; + +import java.util.Locale; + +import org.olat.core.commons.persistence.SortKey; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel; +import org.olat.modules.bigbluebutton.BigBlueButtonServer; + +/** + * + * Initial date: 7 avr. 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class BigBlueButtonAdminServersTableModel extends DefaultFlexiTableDataModel<BigBlueButtonServer> +implements SortableFlexiTableDataModel<BigBlueButtonServer> { + + private final Locale locale; + + public BigBlueButtonAdminServersTableModel(FlexiTableColumnModel columnsModel, Locale locale) { + super(columnsModel); + this.locale = locale; + } + + @Override + public void sort(SortKey sortKey) { + // + } + + @Override + public Object getValueAt(int row, int col) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Object getValueAt(BigBlueButtonServer row, int col) { + return null; + } + + @Override + public DefaultFlexiTableDataModel<BigBlueButtonServer> createCopyWithEmptyList() { + return null; + } + + + +} diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonConfigurationController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonConfigurationController.java index 24b9f3a7c8fc62a0e51888c9f532bd5dcb014356..23e972292492f35242e2b4c63bfad4cadc4a7fcb 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonConfigurationController.java +++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonConfigurationController.java @@ -19,28 +19,31 @@ */ package org.olat.modules.bigbluebutton.ui; -import java.net.URI; -import java.net.URISyntaxException; +import java.util.List; import org.olat.collaboration.CollaborationToolsFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItem; import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement; import org.olat.core.gui.components.form.flexible.elements.FormLink; import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; -import org.olat.core.gui.components.form.flexible.elements.SpacerElement; -import org.olat.core.gui.components.form.flexible.elements.TextElement; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; import org.olat.core.gui.components.form.flexible.impl.FormEvent; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; -import org.olat.core.util.StringHelper; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; import org.olat.modules.bigbluebutton.BigBlueButtonManager; import org.olat.modules.bigbluebutton.BigBlueButtonModule; -import org.olat.modules.bigbluebutton.model.BigBlueButtonErrors; -import org.olat.modules.bigbluebutton.model.BigBlueButtonException; +import org.olat.modules.bigbluebutton.BigBlueButtonServer; +import org.olat.modules.bigbluebutton.ui.BigBlueButtonConfigurationServersTableModel.ConfigServerCols; import org.springframework.beans.factory.annotation.Autowired; /** @@ -52,21 +55,19 @@ import org.springframework.beans.factory.annotation.Autowired; public class BigBlueButtonConfigurationController extends FormBasicController { private static final String[] FOR_KEYS = { "courses", "groups" }; - private static final String PLACEHOLDER = "xxx-placeholder-xxx"; + private static final String[] ENABLED_KEY = new String[]{ "on" }; - private FormLink checkLink; - private TextElement urlEl; - private SpacerElement spacerEl; - private TextElement sharedSecretEl; private MultipleSelectionElement moduleEnabled; private MultipleSelectionElement enabledForEl; private MultipleSelectionElement permanentForEl; - private MultipleSelectionElement adhocForEl; - - private static final String[] enabledKeys = new String[]{"on"}; - private final String[] enabledValues; - private String replacedSharedSecretValue; + private FormLink addServerButton; + private FlexiTableElement serversTableEl; + private BigBlueButtonConfigurationServersTableModel serversTableModel; + + private CloseableModalController cmc; + private EditBigBlueButtonServerController editServerCtlr; + private ConfirmDeleteServerController confirmDeleteServerCtrl; @Autowired private BigBlueButtonModule bigBlueButtonModule; @@ -75,9 +76,10 @@ public class BigBlueButtonConfigurationController extends FormBasicController { public BigBlueButtonConfigurationController(UserRequest ureq, WindowControl wControl) { super(ureq, wControl); - enabledValues = new String[]{translate("enabled")}; + initForm(ureq); updateUI(); + loadModel(); } @Override @@ -85,9 +87,10 @@ public class BigBlueButtonConfigurationController extends FormBasicController { setFormTitle("bigbluebutton.title"); setFormInfo("bigbluebutton.intro"); setFormContextHelp("Communication and Collaboration#_bigbluebutton_config"); + String[] enabledValues = new String[]{ translate("enabled") }; - moduleEnabled = uifactory.addCheckboxesHorizontal("bigbluebutton.module.enabled", formLayout, enabledKeys, enabledValues); - moduleEnabled.select(enabledKeys[0], bigBlueButtonModule.isEnabled()); + moduleEnabled = uifactory.addCheckboxesHorizontal("bigbluebutton.module.enabled", formLayout, ENABLED_KEY, enabledValues); + moduleEnabled.select(ENABLED_KEY[0], bigBlueButtonModule.isEnabled()); moduleEnabled.addActionListener(FormEvent.ONCHANGE); String[] forValues = new String[] { @@ -97,38 +100,28 @@ public class BigBlueButtonConfigurationController extends FormBasicController { enabledForEl.select(FOR_KEYS[0], bigBlueButtonModule.isCoursesEnabled()); enabledForEl.select(FOR_KEYS[1], bigBlueButtonModule.isGroupsEnabled()); - permanentForEl = uifactory.addCheckboxesHorizontal("enable.permanent.meeting", formLayout, enabledKeys, enabledValues); - permanentForEl.select(enabledKeys[0], bigBlueButtonModule.isPermanentMeetingEnabled()); - - adhocForEl = uifactory.addCheckboxesHorizontal("enable.adhoc.meeting", formLayout, enabledKeys, enabledValues); - adhocForEl.select(enabledKeys[0], bigBlueButtonModule.isAdhocMeetingEnabled()); - adhocForEl.setVisible(false);//TODO bbb - - //spacer - spacerEl = uifactory.addSpacerElement("spacer", formLayout, false); + permanentForEl = uifactory.addCheckboxesHorizontal("enable.permanent.meeting", formLayout, ENABLED_KEY, enabledValues); + permanentForEl.select(ENABLED_KEY[0], bigBlueButtonModule.isPermanentMeetingEnabled()); - URI uri = bigBlueButtonModule.getBigBlueButtonURI(); - String uriStr = uri == null ? "" : uri.toString(); - urlEl = uifactory.addTextElement("bbb-url", "option.baseurl", 255, uriStr, formLayout); - urlEl.setDisplaySize(60); - urlEl.setExampleKey("option.baseurl.example", null); - urlEl.setMandatory(true); - - String sharedSecret = bigBlueButtonModule.getSharedSecret(); - if(StringHelper.containsNonWhitespace(sharedSecret)) { - replacedSharedSecretValue = sharedSecret; - sharedSecret = PLACEHOLDER; - } - sharedSecretEl = uifactory.addPasswordElement("shared.secret", "option.bigbluebutton.shared.secret", 255, sharedSecret, formLayout); - sharedSecretEl.setAutocomplete("new-password"); - sharedSecretEl.setMandatory(true); + FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ConfigServerCols.url)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ConfigServerCols.enabled)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("edit", translate("edit"), "edit")); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("delete", translate("delete"), "delete")); + serversTableModel = new BigBlueButtonConfigurationServersTableModel(columnsModel, getLocale()); + serversTableEl = uifactory.addTableElement(getWindowControl(), "servers", serversTableModel, 10, false, getTranslator(), formLayout); + serversTableEl.setCustomizeColumns(false); + serversTableEl.setNumOfRowsEnabled(false); + serversTableEl.setLabel("bigbluebutton.servers", null); + serversTableEl.setEmtpyTableMessageKey("bigbluebutton.servers.empty"); + addServerButton = uifactory.addFormLink("add.server", formLayout, Link.BUTTON); + //buttons save - check FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("save", getTranslator()); formLayout.add(buttonLayout); uifactory.addFormSubmitButton("save", buttonLayout); - checkLink = uifactory.addFormLink("check", buttonLayout, Link.BUTTON); } @Override @@ -138,13 +131,16 @@ public class BigBlueButtonConfigurationController extends FormBasicController { private void updateUI() { boolean enabled = moduleEnabled.isAtLeastSelected(1); - adhocForEl.setVisible(false); permanentForEl.setVisible(enabled); enabledForEl.setVisible(enabled); - checkLink.setVisible(enabled); - urlEl.setVisible(enabled); - sharedSecretEl.setVisible(enabled); - spacerEl.setVisible(enabled); + serversTableEl.setVisible(enabled); + addServerButton.setVisible(enabled); + } + + private void loadModel() { + List<BigBlueButtonServer> servers = bigBlueButtonManager.getServers(); + serversTableModel.setObjects(servers); + serversTableEl.reset(true, true, true); } @Override @@ -153,67 +149,52 @@ public class BigBlueButtonConfigurationController extends FormBasicController { //validate only if the module is enabled if(moduleEnabled.isAtLeastSelected(1)) { - allOk &= validateUrlFields(); - if(allOk) { - allOk &= validateConnection(); + if(serversTableModel.getRowCount() == 0) { + serversTableEl.setErrorKey("form.legende.mandatory", null); + allOk &= false; } } return allOk; } - private boolean validateUrlFields() { - boolean allOk = true; - - String url = urlEl.getValue(); - urlEl.clearError(); - if(StringHelper.containsNonWhitespace(url)) { - try { - URI uri = new URI(url); - uri.getHost(); - } catch(Exception e) { - urlEl.setErrorKey("error.url.invalid", null); - allOk &= false; + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(editServerCtlr == source || confirmDeleteServerCtrl == source) { + if(event == Event.CHANGED_EVENT || event == Event.DONE_EVENT) { + loadModel(); } - } else { - urlEl.setErrorKey("form.legende.mandatory", null); - allOk &= false; + cmc.deactivate(); + cleanUp(); + } else if(cmc == source) { + cleanUp(); } - - String password = sharedSecretEl.getValue(); - sharedSecretEl.clearError(); - if(!StringHelper.containsNonWhitespace(password)) { - sharedSecretEl.setErrorKey("form.legende.mandatory", null); - allOk &= false; - } - - return allOk; + super.event(ureq, source, event); } - private boolean validateConnection() { - boolean allOk = true; - try { - BigBlueButtonErrors errors = new BigBlueButtonErrors(); - boolean ok = checkConnection(errors); - if(!ok || errors.hasErrors()) { - sharedSecretEl.setValue(""); - urlEl.setErrorKey("error.connectionValidationFailed", new String[] {errors.getErrorMessages()}); - allOk &= false; - } - } catch (Exception e) { - showError(BigBlueButtonException.SERVER_NOT_I18N_KEY); - allOk &= false; - } - return allOk; + private void cleanUp() { + removeAsListenerAndDispose(confirmDeleteServerCtrl); + removeAsListenerAndDispose(editServerCtlr); + removeAsListenerAndDispose(cmc); + confirmDeleteServerCtrl = null; + editServerCtlr = null; + cmc = null; } - + @Override protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { if(source == moduleEnabled) { updateUI(); - } else if(source == checkLink) { - if(validateUrlFields()) { - doCheckConnection(); + } else if(addServerButton == source) { + addServer(ureq); + } else if(serversTableEl == source) { + if(event instanceof SelectionEvent) { + SelectionEvent se = (SelectionEvent)event; + if("edit".equals(se.getCommand())) { + doEditServer(ureq, serversTableModel.getObject(se.getIndex())); + } else if("delete".equals(se.getCommand())) { + doConfirmDelete(ureq, serversTableModel.getObject(se.getIndex())); + } } } super.formInnerEvent(ureq, source, event); @@ -221,63 +202,50 @@ public class BigBlueButtonConfigurationController extends FormBasicController { @Override protected void formOK(UserRequest ureq) { - try { - boolean enabled = moduleEnabled.isSelected(0); - bigBlueButtonModule.setEnabled(enabled); - // update collaboration tools list - if(enabled) { - String url = urlEl.getValue(); - bigBlueButtonModule.setBigBlueButtonURI(new URI(url)); - bigBlueButtonModule.setCoursesEnabled(enabledForEl.isSelected(0)); - bigBlueButtonModule.setGroupsEnabled(enabledForEl.isSelected(1)); - bigBlueButtonModule.setPermanentMeetingEnabled(permanentForEl.isAtLeastSelected(1)); - bigBlueButtonModule.setAdhocMeetingEnabled(adhocForEl.isAtLeastSelected(1)); - - String sharedSecret = sharedSecretEl.getValue(); - if(!PLACEHOLDER.equals(sharedSecret)) { - bigBlueButtonModule.setSharedSecret(sharedSecret); - sharedSecretEl.setValue(PLACEHOLDER); - } else if(StringHelper.containsNonWhitespace(replacedSharedSecretValue)) { - bigBlueButtonModule.setSharedSecret(replacedSharedSecretValue); - } - } else { - bigBlueButtonModule.setBigBlueButtonURI(null); - bigBlueButtonModule.setSecret(null); - bigBlueButtonModule.setSharedSecret(null); - } - CollaborationToolsFactory.getInstance().initAvailableTools(); - } catch (URISyntaxException e) { - logError("", e); - urlEl.setErrorKey("error.url.invalid", null); + boolean enabled = moduleEnabled.isSelected(0); + bigBlueButtonModule.setEnabled(enabled); + // update collaboration tools list + if(enabled) { + bigBlueButtonModule.setCoursesEnabled(enabledForEl.isSelected(0)); + bigBlueButtonModule.setGroupsEnabled(enabledForEl.isSelected(1)); + bigBlueButtonModule.setPermanentMeetingEnabled(permanentForEl.isAtLeastSelected(1)); } + CollaborationToolsFactory.getInstance().initAvailableTools(); } - private void doCheckConnection() { - BigBlueButtonErrors errors = new BigBlueButtonErrors(); - boolean loginOk = checkConnection(errors); - if(errors.hasErrors()) { - getWindowControl().setError(BigBlueButtonErrorHelper.formatErrors(getTranslator(), errors)); - } else if(loginOk) { - showInfo("connection.successful"); - } else { - showError("connection.failed"); - } + private void addServer(UserRequest ureq) { + if(guardModalController(editServerCtlr)) return; + + editServerCtlr = new EditBigBlueButtonServerController(ureq, getWindowControl()); + listenTo(editServerCtlr); + + cmc = new CloseableModalController(getWindowControl(), "close", editServerCtlr.getInitialComponent(), + true, translate("add.single.meeting")); + cmc.activate(); + listenTo(cmc); } - private boolean checkConnection(BigBlueButtonErrors errors) { - String url = urlEl.getValue(); - String sharedSecret = sharedSecretEl.getValue(); - if(PLACEHOLDER.equals(sharedSecret)) { - if(StringHelper.containsNonWhitespace(replacedSharedSecretValue)) { - sharedSecret = replacedSharedSecretValue; - } else { - sharedSecret = bigBlueButtonModule.getSharedSecret(); - } - } else { - replacedSharedSecretValue = sharedSecret; - sharedSecretEl.setValue(PLACEHOLDER); - } + private void doEditServer(UserRequest ureq, BigBlueButtonServer server) { + if(guardModalController(editServerCtlr)) return; + + editServerCtlr = new EditBigBlueButtonServerController(ureq, getWindowControl(), server); + listenTo(editServerCtlr); + + String title = translate("edit.server", new String[] { server.getUrl() }); + cmc = new CloseableModalController(getWindowControl(), "close", editServerCtlr.getInitialComponent(), true, title); + cmc.activate(); + listenTo(cmc); + } + + private void doConfirmDelete(UserRequest ureq, BigBlueButtonServer server) { + if(guardModalController(confirmDeleteServerCtrl)) return; + + confirmDeleteServerCtrl = new ConfirmDeleteServerController(ureq, getWindowControl(), server); + listenTo(confirmDeleteServerCtrl); - return bigBlueButtonManager.checkConnection(url, sharedSecret, errors); + String title = translate("confirm.delete.server.title", new String[] { server.getUrl() }); + cmc = new CloseableModalController(getWindowControl(), "close", confirmDeleteServerCtrl.getInitialComponent(), true, title); + cmc.activate(); + listenTo(cmc); } } diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonConfigurationServersTableModel.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonConfigurationServersTableModel.java new file mode 100644 index 0000000000000000000000000000000000000000..305903dc29176c733a5f59d21be96a1a2626c6c5 --- /dev/null +++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonConfigurationServersTableModel.java @@ -0,0 +1,102 @@ +/** + * <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.ui; + +import java.util.Locale; + +import org.olat.core.commons.persistence.SortKey; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel; +import org.olat.modules.bigbluebutton.BigBlueButtonServer; + +/** + * + * Initial date: 7 avr. 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class BigBlueButtonConfigurationServersTableModel extends DefaultFlexiTableDataModel<BigBlueButtonServer> +implements SortableFlexiTableDataModel<BigBlueButtonServer> { + + private final static ConfigServerCols[] COLS = ConfigServerCols.values(); + + private final Locale locale; + + public BigBlueButtonConfigurationServersTableModel(FlexiTableColumnModel columnsModel, Locale locale) { + super(columnsModel); + this.locale = locale; + } + + @Override + public void sort(SortKey sortKey) { + // + } + + @Override + public Object getValueAt(int row, int col) { + BigBlueButtonServer server = getObject(row); + return getValueAt(server, col); + } + + @Override + public Object getValueAt(BigBlueButtonServer row, int col) { + switch(COLS[col]) { + case url: return row.getUrl(); + case recordingUrl: return row.getRecordingUrl(); + case enabled: return Boolean.valueOf(row.isEnabled()); + default: return "ERROR"; + } + } + + @Override + public BigBlueButtonConfigurationServersTableModel createCopyWithEmptyList() { + return new BigBlueButtonConfigurationServersTableModel(getTableColumnModel(), locale); + } + + public enum ConfigServerCols implements FlexiSortableColumnDef { + + url("table.header.server.url"), + recordingUrl("table.header.server.recording"), + enabled("table.header.server.enabled"); + + private final String i18nHeaderKey; + + private ConfigServerCols(String i18nHeaderKey) { + this.i18nHeaderKey = i18nHeaderKey; + } + + @Override + public boolean sortable() { + return true; + } + + @Override + public String sortKey() { + return name(); + } + + @Override + public String i18nHeaderKey() { + return i18nHeaderKey; + } + } +} diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/ConfirmDeleteServerController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/ConfirmDeleteServerController.java new file mode 100644 index 0000000000000000000000000000000000000000..ecc273d972d48053a8d485bea07be0528c98e55c --- /dev/null +++ b/src/main/java/org/olat/modules/bigbluebutton/ui/ConfirmDeleteServerController.java @@ -0,0 +1,104 @@ +/** + * <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.ui; + +import org.olat.core.commons.persistence.DB; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.modules.bigbluebutton.BigBlueButtonManager; +import org.olat.modules.bigbluebutton.BigBlueButtonServer; +import org.olat.modules.bigbluebutton.model.BigBlueButtonErrors; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 8 avr. 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ConfirmDeleteServerController extends FormBasicController { + + private FormLink deleteLink; + + private BigBlueButtonServer server; + + @Autowired + private DB dbInstance; + @Autowired + private BigBlueButtonManager bigBlueButtonManager; + + public ConfirmDeleteServerController(UserRequest ureq, WindowControl wControl, BigBlueButtonServer server) { + super(ureq, wControl, "confirm_delete_server"); + this.server = server; + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + if(formLayout instanceof FormLayoutContainer) { + FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout; + String msg = translate("confirm.delete.server", new String[] { server.getUrl() }); + layoutCont.contextPut("msg", msg); + } + + uifactory.addFormCancelButton("cancel", formLayout, ureq, getWindowControl()); + deleteLink = uifactory.addFormLink("delete", formLayout, Link.BUTTON); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(deleteLink == source) { + doDelete(ureq); + } + super.formInnerEvent(ureq, source, event); + } + + @Override + protected void formOK(UserRequest ureq) { + // + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } + + private void doDelete(UserRequest ureq) { + BigBlueButtonErrors errors = new BigBlueButtonErrors(); + bigBlueButtonManager.deleteServer(server, errors); + dbInstance.commit(); + fireEvent(ureq, Event.DONE_EVENT); + } + +} diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonServerController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonServerController.java new file mode 100644 index 0000000000000000000000000000000000000000..a0acc91b71394b05ec455211b031c308fa299c79 --- /dev/null +++ b/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonServerController.java @@ -0,0 +1,300 @@ +/** + * <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.ui; + +import java.net.URI; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.StringHelper; +import org.olat.modules.bigbluebutton.BigBlueButtonManager; +import org.olat.modules.bigbluebutton.BigBlueButtonServer; +import org.olat.modules.bigbluebutton.model.BigBlueButtonErrors; +import org.olat.modules.bigbluebutton.model.BigBlueButtonException; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 7 avr. 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class EditBigBlueButtonServerController extends FormBasicController { + + private static final String PLACEHOLDER = "xxx-placeholder-xxx"; + private static final String[] onKeys = new String[] { "" }; + + private FormLink checkLink; + private TextElement urlEl; + //private TextElement recordingUrlEl; + private TextElement sharedSecretEl; + private TextElement capacityFactorEl; + private MultipleSelectionElement enabledEl; + + private BigBlueButtonServer server; + private String replacedSharedSecretValue; + + @Autowired + private BigBlueButtonManager bigBlueButtonManager; + + public EditBigBlueButtonServerController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + initForm(ureq); + } + + public EditBigBlueButtonServerController(UserRequest ureq, WindowControl wControl, BigBlueButtonServer server) { + super(ureq, wControl); + this.server = server; + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + String url = server == null ? null : server.getUrl(); + urlEl = uifactory.addTextElement("bbb.url", "option.baseurl", 255, url, formLayout); + urlEl.setDisplaySize(60); + urlEl.setExampleKey("option.baseurl.example", null); + urlEl.setMandatory(true); + + String sharedSecret = server == null ? null : server.getSharedSecret(); + if(StringHelper.containsNonWhitespace(sharedSecret)) { + replacedSharedSecretValue = sharedSecret; + sharedSecret = PLACEHOLDER; + } + sharedSecretEl = uifactory.addPasswordElement("shared.secret", "option.bigbluebutton.shared.secret", 255, sharedSecret, formLayout); + sharedSecretEl.setAutocomplete("new-password"); + sharedSecretEl.setMandatory(true); + + //String recordingUrl = server == null ? null : server.getRecordingUrl(); + //recordingUrlEl = uifactory.addTextElement("bbb.recording.url", "option.recordingurl", 255, recordingUrl, formLayout); + //recordingUrlEl.setDisplaySize(60); + //recordingUrlEl.setExampleKey("option.baseurl.example", null); + + String capacityFactor = server == null || server.getCapacityFactory() == null + ? "1.0" : server.getCapacityFactory().toString(); + capacityFactorEl = uifactory.addTextElement("bbb.capacity", "option.capacity.factory", 255, capacityFactor, formLayout); + capacityFactorEl.setDisplaySize(60); + capacityFactorEl.setExampleKey("option.capacity.factor.example", null); + + String[] onValues = new String[] { translate("enabled") }; + enabledEl = uifactory.addCheckboxesVertical("option.enabled.server", formLayout, onKeys, onValues, 1); + enabledEl.select(onKeys[0], server == null || server.isEnabled()); + + FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + formLayout.add("buttons", buttonLayout); + uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl()); + uifactory.addFormSubmitButton("save", buttonLayout); + checkLink = uifactory.addFormLink("check", buttonLayout, Link.BUTTON); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + boolean allOk = super.validateFormLogic(ureq); + + allOk &= validateUrlFields(); + if(allOk) { + allOk &= validateConnection(); + + if((server == null || server.getKey() == null) + && bigBlueButtonManager.hasServer(urlEl.getValue())) { + urlEl.setErrorKey("error.server.exists", null); + allOk &= false; + } + } + + return allOk; + } + + private boolean validateUrlFields() { + boolean allOk = true; + + allOk &= validateUrl(urlEl, true); + //allOk &= validateUrl(recordingUrlEl, false); + + capacityFactorEl.clearError(); + if(StringHelper.containsNonWhitespace(capacityFactorEl.getValue())) { + try { + String factor = capacityFactorEl.getValue(); + double capacityFactory = Double.parseDouble(factor); + if(capacityFactory < 1.0 || capacityFactory > 100.0) { + capacityFactorEl.setErrorKey("error.capacity.factory", null); + allOk &= false; + } + } catch (NumberFormatException e) { + capacityFactorEl.setErrorKey("error.capacity.factory", null); + allOk &= false; + } + } else { + capacityFactorEl.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + + String password = sharedSecretEl.getValue(); + sharedSecretEl.clearError(); + if(!StringHelper.containsNonWhitespace(password)) { + sharedSecretEl.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + + return allOk; + } + + private boolean validateUrl(TextElement el, boolean mandatory) { + boolean allOk = true; + + String url = el.getValue(); + el.clearError(); + if(StringHelper.containsNonWhitespace(url)) { + try { + URI uri = new URI(url); + uri.getHost(); + } catch(Exception e) { + el.setErrorKey("error.url.invalid", null); + allOk &= false; + } + } else if(mandatory) { + el.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + + return allOk; + } + + private boolean validateConnection() { + boolean allOk = true; + try { + BigBlueButtonErrors errors = new BigBlueButtonErrors(); + boolean ok = checkConnection(errors); + if(!ok || errors.hasErrors()) { + sharedSecretEl.setValue(""); + urlEl.setErrorKey("error.connectionValidationFailed", new String[] {errors.getErrorMessages()}); + allOk &= false; + } + } catch (Exception e) { + showError(BigBlueButtonException.SERVER_NOT_I18N_KEY); + allOk &= false; + } + return allOk; + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(source == checkLink) { + if(validateUrlFields()) { + doCheckConnection(); + } + } + super.formInnerEvent(ureq, source, event); + } + + private Double getCapacityFactory() { + String val = capacityFactorEl.getValue(); + Double factor = null; + if(StringHelper.containsNonWhitespace(val)) { + try { + factor = Double.valueOf(val); + } catch (NumberFormatException e) { + logWarn("Cannot parse: " + val, null); + } + } + + if(factor == null || factor.doubleValue() < 1.0d) { + factor = Double.valueOf(1.0d); + } + return factor; + } + + @Override + protected void formOK(UserRequest ureq) { + if(server == null) { + String url = urlEl.getValue(); + String sharedSecret = sharedSecretEl.getValue(); + if(!PLACEHOLDER.equals(sharedSecret)) { + sharedSecretEl.setValue(PLACEHOLDER); + } else if(StringHelper.containsNonWhitespace(replacedSharedSecretValue)) { + sharedSecret = replacedSharedSecretValue; + } + server = bigBlueButtonManager.createServer(url, null, sharedSecret); + } else { + server.setUrl(urlEl.getValue()); + String sharedSecret = sharedSecretEl.getValue(); + if(!PLACEHOLDER.equals(sharedSecret)) { + server.setSharedSecret(sharedSecret); + sharedSecretEl.setValue(PLACEHOLDER); + } else if(StringHelper.containsNonWhitespace(replacedSharedSecretValue)) { + server.setSharedSecret(replacedSharedSecretValue); + } + } + + server.setEnabled(enabledEl.isAtLeastSelected(1)); + server.setCapacityFactory(getCapacityFactory()); + server = bigBlueButtonManager.updateServer(server); + fireEvent(ureq, Event.DONE_EVENT); + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } + + private void doCheckConnection() { + BigBlueButtonErrors errors = new BigBlueButtonErrors(); + boolean loginOk = checkConnection(errors); + if(errors.hasErrors()) { + getWindowControl().setError(BigBlueButtonErrorHelper.formatErrors(getTranslator(), errors)); + } else if(loginOk) { + showInfo("connection.successful"); + } else { + showError("connection.failed"); + } + } + + private boolean checkConnection(BigBlueButtonErrors errors) { + String url = urlEl.getValue(); + String sharedSecret = sharedSecretEl.getValue(); + if(PLACEHOLDER.equals(sharedSecret)) { + if(StringHelper.containsNonWhitespace(replacedSharedSecretValue)) { + sharedSecret = replacedSharedSecretValue; + } else if(server != null) { + sharedSecret = server.getSharedSecret(); + } + } else { + replacedSharedSecretValue = sharedSecret; + sharedSecretEl.setValue(PLACEHOLDER); + } + return bigBlueButtonManager.checkConnection(url, sharedSecret, errors); + } +} diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_content/confirm_delete_server.html b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/confirm_delete_server.html new file mode 100644 index 0000000000000000000000000000000000000000..4b4a2d1b9c0ee54698d07a19ada4d418f51c5fe2 --- /dev/null +++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/confirm_delete_server.html @@ -0,0 +1,8 @@ +<div class="o_warning" role="alert"> + <i class="o_icon o_icon-lg o_icon_important"> </i> $msg +</div> + +<div class="o_button_group"> + $r.render("cancel") + $r.render("delete") +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties index 0e0a528172e430b9b24d9593b5a272f219a85b7e..70606ea5a4cc1ec8e12284887cccaa9dc97f100a 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties @@ -3,6 +3,7 @@ account.configuration=Konfiguration add.daily.meeting=T\u00E4glich wiederkehrende Online-Termin hinzuf\u00FCgen add.meeting=Online-Termin hinzuf\u00FCgen add.permanent.meeting=Online-Termin ohne Datum hinzuf\u00FCgen +add.server=Server hinzuf\u00FCgen add.single.meeting=Einzige Online-Termin hinzuf\u00FCgen add.template=Raumvorlage erstellen add.weekly.meeting=W\u00F6chentlich wiederkehrende Online-Termin hinzuf\u00FCgen @@ -13,6 +14,8 @@ bigbluebutton.module.enabled=Modul "BigBlueButton" bigbluebutton.module.enabled.for=Aktivieren f\u00FCr bigbluebutton.module.enabled.for.courses=Kurse bigbluebutton.module.enabled.for.groups=Gruppen +bigbluebutton.servers=Server +bigbluebutton.servers.empty=Sie haben noch kein Server konfiguriert bigbluebutton.title=Konfiguration BigBlueButton Web Conferencing Service calendar.open=Raumbuchungen anzeigen calendar.title=Kalender @@ -21,19 +24,24 @@ confirm.delete.meeting=Wollen Sie wirklich den Online-Termin "{0}" l\u00F6schen? confirm.delete.meeting.title=Online-Termin "{0}" l\u00F6schen confirm.delete.meetings=Wollen Sie wirklich {0} Online-Termine ({1}) l\u00F6schen? confirm.delete.meetings.title={0} Online-Termine l\u00F6schen +confirm.delete.server=Wollen Sie wirklich den Server "{0}" l\u00F6schen? <strong>Alle Meetings und Aufzeichnungen werden auch gel\u00F6scht.</strong> +confirm.delete.server.title=Server "{0}" l\u00F6schen confirm.delete.template=Wollen Sie wirklich die Raumvorlage "{0}" l\u00F6schen? confirm.delete.template.title=Raumvorlage "{0}" l\u00F6schen connection.failed=BigBlueButton API Login fehlgeschlagen. connection.successful=BigBlueButton API Login erfolgreich\! +edit.server=Server "{0}" bearbeiten edit.template=Raumvorlage "{0}" bearbeiten enable.permanent.meeting=Online-Termine ohne Datum enable.adhoc.meeting=Adhoc Online-Termin +error.capacity.factory=Ein Nummer zwischen 1.0 und 100.0 error.connectionValidationFailed=Die Verbindungspr\u00fcfung ist fehlgeschlagen: <pre>{0}</pre> error.date.in.past=Der Termin kann sich nicht in der Vergangenheit befinden. error.duration=Termindauer \u00fcberschritten. Maximal Dauer: {0} Minuten. error.end.past=Der Online-Termin kann nicht in Vergangenheit geplant werden. error.prefix=Ein Fehler ist aufgetreten\: error.same.day=Sie haben schon ein Meeting an diesem Tag geplant. +error.server.exists=Ein Server mit diesem URL existiert schon. error.server.raw={1} <small>Schl\u00FCssel\: {0}</small> error.start.after.end=Das Enddatum darf nicht vor dem Beginndatum sein. error.too.long.time=Zeit ist zu lang. Es sind maximal {0} Minuten erlaubt. @@ -71,6 +79,10 @@ option.baseurl=BigBlueButton API URL option.baseurl.example=https\://bigbluebutton.openolat.com/bigbluebutton/ option.bigbluebutton.secret=Secret option.bigbluebutton.shared.secret=Shared secret +option.capacity.factory=Capacity factor +option.capacity.factor.example=Ein Nummer zwischen 1 und 100 (zum Beispiel 1.23) +option.enabled.server=Server aktivieren +option.recordingurl=Aufzeichnung URL recordings=Aufzeichnungen recording.browser.infos=Aufzeichnungen k\u00F6nnen nur mit Google Chrome und Firefox gesehen werden. recording.type.podcast=Podcast @@ -81,6 +93,7 @@ role.coach=Betreuer role.owner=Kursbesitzer role.group=Gruppenmitglied server.overloaded=F\u00FCr das gew\u00E4hlten Datum/Uhrzeit ist kein Raum verf\u00FCgbar. W\u00E4hlen Sie ein anderes Datum/Uhrzeit oder eine andere Raumvorlage. +servers.title=Server table.header.day.week=Tag table.header.available=Verf\u00FCgbarkeit table.header.enabled=Aktiv @@ -94,6 +107,9 @@ table.header.recording.type=Typ table.header.recording.open=\u00D6ffnen table.header.recording.start=Beginn table.header.recording.end=Ende +table.header.server.enabled=Eingeschaltet +table.header.server.recording=Aufzeichnungen URL +table.header.server.url=URL table.header.template=Raumvorlage table.header.webcams.only.moderator=Nur Moderatorenkamera template.allowModsToUnmuteUsers=Moderatoren d\u00FCrfen Teilnehmer-Mikrofon aktiveren diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties index 9366f77bcd070620138ecc8165e9ac01e3af4961..2050e895532ff1748cd15a4ac63effb99c956f3a 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties @@ -13,6 +13,8 @@ bigbluebutton.module.enabled=Module "BigBlueButton" bigbluebutton.module.enabled.for=Activate for bigbluebutton.module.enabled.for.courses=Courses bigbluebutton.module.enabled.for.groups=Groups +bigbluebutton.servers=Servers +bigbluebutton.servers.empty=You don't have configured a server. bigbluebutton.title=Configuration of BigBlueButton Web Conferencing service calendar.open=Show room bookings calendar.title=Calendar @@ -21,19 +23,24 @@ confirm.delete.meeting=Do you really want to delete the online-meeting "{0}"? confirm.delete.meeting.title=Delete online-meeting "{0}" confirm.delete.meetings=Do you really want to delete the {0} online-meetings ({1})? confirm.delete.meetings.title=Delete {0} online-meetings +confirm.delete.server=Do you really want to delete the server "{0}"? <strong>All meetings and recordings will be deleted too.</strong> +confirm.delete.server.title=Delete server "{0}" confirm.delete.template=Do you really want to delete the room-template "{0}"? confirm.delete.template.title=Delete room-template "{0}" connection.failed=BigBlueButton API login failed. connection.successful=BigBlueButton API login successful\! +edit.server=Edit server "{0}" edit.template=Edit room-template "{0}" enable.permanent.meeting=Online-Meetings without date enable.adhoc.meeting=Adhoc online-meeting +error.capacity.factory=A number between 1.0 and 100.0 error.connectionValidationFailed=The connection validation failed: <pre>{0}</pre> error.date.in.past=The meeting date can not be in the past. error.duration=Meeting duration is too long. Maximal duration: {0} minutes. error.end.past=Online-meeting cannot be planned in the past. error.prefix=An error happened\: error.same.day=You already have a meeting planed at this date. +error.server.exists=A server with this URL already exists. error.server.raw={1} <small>Key {0}</small> error.start.after.end=The end date must not be before the start date. error.too.long.time=Time is too long. It is limited to {0} minutes. @@ -71,6 +78,10 @@ option.baseurl=BigBlueButton API URL option.baseurl.example=https\://bigbluebutton.openolat.com/bigbluebutton/ option.bigbluebutton.secret=Secret option.bigbluebutton.shared.secret=Shared secret +option.capacity.factory=Capacity factor +option.capacity.factor.example=A number between 1 and 100 (for example: 1.23) +option.enabled.server=Activate server +option.recordingurl=Recording URL recordings=Recordings recording.browser.infos=Recordings can only be viewed with Google Chrome or Firefox. recording.type.podcast=Podcast @@ -81,6 +92,7 @@ role.coach=Coach role.owner=Course owner role.group=Group user server.overloaded=There is no room available for the choosen date/time. Choose another date/time or another room template. +servers.title=Servers table.header.day.week=Day table.header.enabled=Enabled table.header.permanent=Withour date @@ -93,6 +105,9 @@ table.header.recording.type=Type table.header.recording.open=Open table.header.recording.start=Start table.header.recording.end=End +table.header.server.enabled=Enabled +table.header.server.recording=Recording URL +table.header.server.url=URL table.header.template=Room-template table.header.webcams.only.moderator=Webcams only for moderators template.allowModsToUnmuteUsers=Allow moderators to activate the participants microphone diff --git a/src/main/java/org/olat/upgrade/OLATUpgrade_14_2_7.java b/src/main/java/org/olat/upgrade/OLATUpgrade_14_2_7.java new file mode 100644 index 0000000000000000000000000000000000000000..02c80b0d500119d03c3ca9953e03f212125a2c84 --- /dev/null +++ b/src/main/java/org/olat/upgrade/OLATUpgrade_14_2_7.java @@ -0,0 +1,143 @@ +/** + * <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.upgrade; + +import java.net.URI; +import java.util.Date; +import java.util.List; + +import javax.persistence.TemporalType; + +import org.apache.logging.log4j.Logger; +import org.olat.core.commons.persistence.DB; +import org.olat.core.commons.persistence.QueryBuilder; +import org.olat.core.logging.Tracing; +import org.olat.modules.bigbluebutton.BigBlueButtonManager; +import org.olat.modules.bigbluebutton.BigBlueButtonMeeting; +import org.olat.modules.bigbluebutton.BigBlueButtonModule; +import org.olat.modules.bigbluebutton.BigBlueButtonServer; +import org.olat.modules.bigbluebutton.model.BigBlueButtonMeetingImpl; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 2 avr. 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class OLATUpgrade_14_2_7 extends OLATUpgrade { + + private static final Logger log = Tracing.createLoggerFor(OLATUpgrade_14_2_7.class); + + private static final String VERSION = "OLAT_14.2.7"; + private static final String BIGBLUEBUTTON_TO_DB = "BIGBLUEBUTTON SERVER TO DB"; + + @Autowired + private DB dbInstance; + @Autowired + private BigBlueButtonModule bigBlueButtonModule; + @Autowired + private BigBlueButtonManager bigBlueButtonManager; + + public OLATUpgrade_14_2_7() { + super(); + } + + @Override + public String getVersion() { + return VERSION; + } + + @Override + public boolean doPostSystemInitUpgrade(UpgradeManager upgradeManager) { + UpgradeHistoryData uhd = upgradeManager.getUpgradesHistory(VERSION); + if (uhd == null) { + // has never been called, initialize + uhd = new UpgradeHistoryData(); + } else if (uhd.isInstallationComplete()) { + return false; + } + + boolean allOk = true; + allOk &= migrateBigBlueButtonServer(upgradeManager, uhd); + + uhd.setInstallationComplete(allOk); + upgradeManager.setUpgradesHistory(uhd, VERSION); + if(allOk) { + log.info(Tracing.M_AUDIT, "Finished OLATUpgrade_14_2_7 successfully!"); + } else { + log.info(Tracing.M_AUDIT, "OLATUpgrade_14_2_67not finished, try to restart OpenOlat!"); + } + return allOk; + } + + private boolean migrateBigBlueButtonServer(UpgradeManager upgradeManager, UpgradeHistoryData uhd) { + boolean allOk = true; + if (!uhd.getBooleanDataValue(BIGBLUEBUTTON_TO_DB)) { + + if(bigBlueButtonModule.isEnabled()) { + URI uri = bigBlueButtonModule.getBigBlueButtonURI(); + String sharedSecret = bigBlueButtonModule.getSharedSecret(); + if(uri != null && sharedSecret != null) { + String uriStr = uri.toString(); + BigBlueButtonServer server = null;; + if(!bigBlueButtonManager.hasServer(uriStr)) { + server = bigBlueButtonManager.createServer(uriStr, null, sharedSecret); + dbInstance.commitAndCloseSession(); + } else { + List<BigBlueButtonServer> servers = bigBlueButtonManager.getServers(); + for(BigBlueButtonServer potentialServer:servers) { + if(uriStr.startsWith(potentialServer.getUrl()) || potentialServer.getUrl().startsWith(uriStr)) { + server = potentialServer; + } + } + } + if(server != null) { + migrateBigBlueButtonServer(server); + dbInstance.commitAndCloseSession(); + } + } + } + + uhd.setBooleanDataValue(BIGBLUEBUTTON_TO_DB, allOk); + upgradeManager.setUpgradesHistory(uhd, VERSION); + } + return allOk; + } + + private void migrateBigBlueButtonServer(BigBlueButtonServer server) { + List<BigBlueButtonMeeting> meetings = getMeetingsToMigrate(); + for(BigBlueButtonMeeting meeting:meetings) { + ((BigBlueButtonMeetingImpl)meeting).setServer(server); + bigBlueButtonManager.updateMeeting(meeting); + } + } + + private List<BigBlueButtonMeeting> getMeetingsToMigrate() { + QueryBuilder sb = new QueryBuilder(); + sb.append("select meeting from bigbluebuttonmeeting as meeting") + .append(" where meeting.server.key is null and (meeting.permanent=true or meeting.startDate<=:now)"); + + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), BigBlueButtonMeeting.class) + .setParameter("now", new Date(), TemporalType.TIMESTAMP) + .getResultList(); + } +} diff --git a/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml b/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml index e7f846dafd6c5113dde494eabaa7a0270d61a256..daca3a1c382b6a9043006f46d0d8dd8f95fb5a11 100644 --- a/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml +++ b/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml @@ -55,6 +55,7 @@ <bean id="upgrade_14_1_0" class="org.olat.upgrade.OLATUpgrade_14_1_0"/> <bean id="upgrade_14_2_0" class="org.olat.upgrade.OLATUpgrade_14_2_0"/> <bean id="upgrade_14_2_6" class="org.olat.upgrade.OLATUpgrade_14_2_6"/> + <bean id="upgrade_14_2_7" class="org.olat.upgrade.OLATUpgrade_14_2_7"/> </list> </property> </bean> diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml index f0e5e70a1cfda75776b73d864f8a81bb8af5578d..2ae89276644d1f0b679ed406b41e8e24c71944cb 100644 --- a/src/main/resources/META-INF/persistence.xml +++ b/src/main/resources/META-INF/persistence.xml @@ -175,6 +175,7 @@ <class>org.olat.modules.assessment.model.AssessmentEntryImpl</class> <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.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_14_2_x_to_14_2_7.sql b/src/main/resources/database/mysql/alter_14_2_x_to_14_2_7.sql new file mode 100644 index 0000000000000000000000000000000000000000..df320052edca28f728d6fad2616964d3949a94d4 --- /dev/null +++ b/src/main/resources/database/mysql/alter_14_2_x_to_14_2_7.sql @@ -0,0 +1,18 @@ +create table o_bbb_server ( + id bigint not null auto_increment, + creationdate datetime not null, + lastmodified datetime not null, + b_name varchar(128), + b_url varchar(255) not null, + b_shared_secret varchar(255), + b_recording_url varchar(255), + b_enabled bool default true, + b_capacity_factor decimal, + primary key (id) +); +alter table o_bbb_server ENGINE = InnoDB; + +alter table o_bbb_meeting add column fk_server_id bigint; + +alter table o_bbb_meeting add constraint bbb_meet_serv_idx foreign key (fk_server_id) references o_bbb_server (id); + diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql index d29684418a72a1e645cfb682a41fe7d598cc0c6e..16eea6d77e6ce4766c240f2777f79396cb79530e 100644 --- a/src/main/resources/database/mysql/setupDatabase.sql +++ b/src/main/resources/database/mysql/setupDatabase.sql @@ -1251,6 +1251,19 @@ create table o_bbb_template ( primary key (id) ); +create table o_bbb_server ( + id bigint not null auto_increment, + creationdate datetime not null, + lastmodified datetime not null, + b_name varchar(128), + b_url varchar(255) not null, + b_shared_secret varchar(255), + b_recording_url varchar(255), + b_enabled bool default true, + b_capacity_factor decimal, + primary key (id) +); + create table o_bbb_meeting ( id bigint not null auto_increment, creationdate datetime not null, @@ -1272,6 +1285,7 @@ create table o_bbb_meeting ( a_sub_ident varchar(64) default null, fk_group_id bigint default null, fk_template_id bigint default null, + fk_server_id bigint, primary key (id) ); @@ -3299,6 +3313,7 @@ alter table o_aconnect_meeting ENGINE = InnoDB; 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_im_message ENGINE = InnoDB; alter table o_im_notification ENGINE = InnoDB; alter table o_im_roster_entry ENGINE = InnoDB; @@ -3686,6 +3701,8 @@ alter table o_bbb_meeting add constraint bbb_meet_entry_idx foreign key (fk_entr alter table o_bbb_meeting add constraint bbb_meet_grp_idx foreign key (fk_group_id) references o_gp_business (group_id); alter table o_bbb_meeting add constraint bbb_meet_template_idx foreign key (fk_template_id) references o_bbb_template (id); +alter table o_bbb_meeting add constraint bbb_meet_serv_idx foreign key (fk_server_id) references o_bbb_server (id); + -- eportfolio alter table o_ep_artefact add constraint FKF26C8375236F28X foreign key (fk_artefact_auth_id) references o_bs_identity (id); alter table o_ep_artefact add constraint FKA0070D12316A97B4 foreign key (fk_struct_el_id) references o_ep_struct_el (structure_id); diff --git a/src/main/resources/database/oracle/alter_14_2_x_to_14_2_7.sql b/src/main/resources/database/oracle/alter_14_2_x_to_14_2_7.sql new file mode 100644 index 0000000000000000000000000000000000000000..6fd06102c8060c777afbd8130c7193405263a908 --- /dev/null +++ b/src/main/resources/database/oracle/alter_14_2_x_to_14_2_7.sql @@ -0,0 +1,18 @@ +create table o_bbb_server ( + id number(20) generated always as identity, + creationdate timestamp not null, + lastmodified timestamp not null, + b_name varchar(128), + b_url varchar(255) not null, + b_shared_secret varchar(255), + b_recording_url varchar(255), + b_enabled number default 1 not null, + b_capacity_factor decimal, + primary key (id) +); + +alter table o_bbb_meeting add fk_server_id number(20); + +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); + diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql index 064778b45c763db3f4d8de0f5e8a87a087849d75..99113c672d9affcbbcbddcdd13b912297e2d93a3 100644 --- a/src/main/resources/database/oracle/setupDatabase.sql +++ b/src/main/resources/database/oracle/setupDatabase.sql @@ -1317,6 +1317,19 @@ create table o_bbb_template ( primary key (id) ); +create table o_bbb_server ( + id number(20) generated always as identity, + creationdate timestamp not null, + lastmodified timestamp not null, + b_name varchar(128), + b_url varchar(255) not null, + b_shared_secret varchar(255), + b_recording_url varchar(255), + b_enabled number default 1 not null, + b_capacity_factor decimal, + primary key (id) +); + create table o_bbb_meeting ( id number(20) generated always as identity, creationdate timestamp not null, @@ -1338,6 +1351,7 @@ create table o_bbb_meeting ( a_sub_ident varchar(64) default null, fk_group_id number(20) default null, fk_template_id number(20) default null, + fk_server_id number(20), primary key (id) ); @@ -3718,6 +3732,8 @@ alter table o_bbb_meeting add constraint bbb_meet_grp_idx foreign key (fk_group_ create index idx_bbb_meet_grp_idx on o_bbb_meeting(fk_group_id); alter table o_bbb_meeting add constraint bbb_meet_template_idx foreign key (fk_template_id) references o_bbb_template (id); 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); -- eportfolio alter table o_ep_artefact add constraint FKF26C8375236F28X foreign key (fk_artefact_auth_id) references o_bs_identity (id); diff --git a/src/main/resources/database/postgresql/alter_14_2_x_to_14_2_7.sql b/src/main/resources/database/postgresql/alter_14_2_x_to_14_2_7.sql new file mode 100644 index 0000000000000000000000000000000000000000..ebb7cc0ce3639be9b3fddd33bf8cbee2462f2de6 --- /dev/null +++ b/src/main/resources/database/postgresql/alter_14_2_x_to_14_2_7.sql @@ -0,0 +1,18 @@ +create table o_bbb_server ( + id bigserial, + creationdate timestamp not null, + lastmodified timestamp not null, + b_name varchar(128), + b_url varchar(255) not null, + b_shared_secret varchar(255), + b_recording_url varchar(255), + b_enabled bool default true, + b_capacity_factor decimal, + primary key (id) +); + +alter table o_bbb_meeting add column fk_server_id bigint; + +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); + diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql index 7af79c0ef67d11304323baf9a7fc8cda05556f00..ef7899ac4ffbeac4e908c55395ba05abe940d985 100644 --- a/src/main/resources/database/postgresql/setupDatabase.sql +++ b/src/main/resources/database/postgresql/setupDatabase.sql @@ -1276,6 +1276,19 @@ create table o_bbb_template ( primary key (id) ); +create table o_bbb_server ( + id bigserial, + creationdate timestamp not null, + lastmodified timestamp not null, + b_name varchar(128), + b_url varchar(255) not null, + b_shared_secret varchar(255), + b_recording_url varchar(255), + b_enabled bool default true, + b_capacity_factor decimal, + primary key (id) +); + create table o_bbb_meeting ( id bigserial, creationdate timestamp not null, @@ -1297,6 +1310,7 @@ create table o_bbb_meeting ( a_sub_ident varchar(64) default null, fk_group_id int8 default null, fk_template_id int8 default null, + fk_server_id int8 default null, primary key (id) ); @@ -3606,6 +3620,8 @@ alter table o_bbb_meeting add constraint bbb_meet_grp_idx foreign key (fk_group_ create index idx_bbb_meet_grp_idx on o_bbb_meeting(fk_group_id); alter table o_bbb_meeting add constraint bbb_meet_template_idx foreign key (fk_template_id) references o_bbb_template (id); 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); -- eportfolio alter table o_ep_artefact add constraint FKF26C8375236F28X foreign key (fk_artefact_auth_id) references o_bs_identity (id); diff --git a/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAOTest.java b/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAOTest.java index 12e6947fd801ec0f78a97f851547cd852a016ac9..895e89f9ead10e2ffd61fc9ea91415757760ba7f 100644 --- a/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAOTest.java +++ b/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAOTest.java @@ -32,6 +32,8 @@ import org.olat.group.BusinessGroup; import org.olat.group.manager.BusinessGroupDAO; import org.olat.modules.bigbluebutton.BigBlueButtonMeeting; import org.olat.modules.bigbluebutton.BigBlueButtonMeetingTemplate; +import org.olat.modules.bigbluebutton.BigBlueButtonServer; +import org.olat.modules.bigbluebutton.model.BigBlueButtonMeetingImpl; import org.olat.repository.RepositoryEntry; import org.olat.test.JunitTestHelper; import org.olat.test.OlatTestCase; @@ -50,6 +52,8 @@ public class BigBlueButtonMeetingDAOTest extends OlatTestCase { @Autowired private BusinessGroupDAO businessGroupDao; @Autowired + private BigBlueButtonServerDAO bigBlueButtonServerDao; + @Autowired private BigBlueButtonMeetingDAO bigBlueButtonMeetingDao; @Autowired private BigBlueButtonMeetingTemplateDAO bigBlueButtonMeetingTemplateDao; @@ -118,6 +122,21 @@ public class BigBlueButtonMeetingDAOTest extends OlatTestCase { Assert.assertNull(reloadedMeeting.getBusinessGroup()); } + @Test + public void loadForUpdate() { + RepositoryEntry entry = JunitTestHelper.createAndPersistRepositoryEntry(); + String name = "BigBlueButton - 2"; + String subIdent = UUID.randomUUID().toString(); + + BigBlueButtonMeeting meeting = bigBlueButtonMeetingDao.createAndPersistMeeting(name, entry, subIdent, null); + dbInstance.commit(); + + + BigBlueButtonMeeting reloadedMeeting = bigBlueButtonMeetingDao.loadForUpdate(meeting); + dbInstance.commit(); + Assert.assertNotNull(reloadedMeeting); + } + @Test public void getMeetingsByRepositoryEntry() { RepositoryEntry entry = JunitTestHelper.createAndPersistRepositoryEntry(); @@ -146,6 +165,27 @@ public class BigBlueButtonMeetingDAOTest extends OlatTestCase { Assert.assertTrue(meetings.contains(meeting)); } + @Test + public void getMeetingsByServer() { + String url = "https://bbb.frentix.com/bigbluebutton"; + String sharedSecret = UUID.randomUUID().toString(); + BigBlueButtonServer server = bigBlueButtonServerDao.createServer(url, null, sharedSecret); + + String name = "BigBlueButton - 7"; + BusinessGroup group = businessGroupDao.createAndPersist(null, "BBB server", "bbb-server", -1, -1, false, false, false, false, false); + BigBlueButtonMeeting meeting = bigBlueButtonMeetingDao.createAndPersistMeeting(name, null, null, group); + dbInstance.commit(); + + ((BigBlueButtonMeetingImpl)meeting).setServer(server); + meeting = bigBlueButtonMeetingDao.updateMeeting(meeting); + dbInstance.commitAndCloseSession(); + + List<BigBlueButtonMeeting> serversMeetings = bigBlueButtonMeetingDao.getMeetings(server); + Assert.assertNotNull(serversMeetings); + Assert.assertEquals(1, serversMeetings.size()); + Assert.assertTrue(serversMeetings.contains(meeting)); + } + @Test public void getConcurrentMeetings() { String externalId = UUID.randomUUID().toString(); diff --git a/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonServerDAOTest.java b/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonServerDAOTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4116106e0914bb24550ba4911797f25bcccd94b5 --- /dev/null +++ b/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonServerDAOTest.java @@ -0,0 +1,76 @@ +/** + * <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.List; +import java.util.UUID; + +import org.junit.Assert; +import org.junit.Test; +import org.olat.core.commons.persistence.DB; +import org.olat.modules.bigbluebutton.BigBlueButtonServer; +import org.olat.test.OlatTestCase; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 7 avr. 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class BigBlueButtonServerDAOTest extends OlatTestCase { + + @Autowired + private DB dbInstance; + @Autowired + private BigBlueButtonServerDAO bigBlueButtonServerDao; + + @Test + public void createServer() { + String url = "https://" + UUID.randomUUID().toString() + "/bigbluebutton"; + String recordingUrl = "https://" + UUID.randomUUID().toString() + "/bigbluebutton/recordings"; + String sharedSecret = UUID.randomUUID().toString(); + + BigBlueButtonServer server = bigBlueButtonServerDao.createServer(url, recordingUrl, sharedSecret); + dbInstance.commitAndCloseSession(); + + Assert.assertNotNull(server); + Assert.assertEquals(url, server.getUrl()); + Assert.assertEquals(recordingUrl, server.getRecordingUrl()); + Assert.assertEquals(sharedSecret, server.getSharedSecret()); + } + + @Test + public void getServers() { + String url = "https://" + UUID.randomUUID().toString() + "/bigbluebutton"; + String recordingUrl = "https://" + UUID.randomUUID().toString() + "/bigbluebutton/recordings"; + String sharedSecret = UUID.randomUUID().toString(); + + BigBlueButtonServer server = bigBlueButtonServerDao.createServer(url, recordingUrl, sharedSecret); + dbInstance.commitAndCloseSession(); + + List<BigBlueButtonServer> servers = bigBlueButtonServerDao.getServers(); + + Assert.assertNotNull(servers); + Assert.assertTrue(servers.contains(server)); + } + + +} diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java index 0c1d58c88cec4d19a8d7c5bc4555b970e1680b38..fcbbdfe5ceb7e6bce31374dcd24b17986c9e13e6 100644 --- a/src/test/java/org/olat/test/AllTestsJunit4.java +++ b/src/test/java/org/olat/test/AllTestsJunit4.java @@ -200,6 +200,7 @@ import org.junit.runners.Suite; org.olat.modules.adobeconnect.manager.AdobeConnectUserDAOTest.class, org.olat.modules.adobeconnect.manager.AdobeConnectMeetingDAOTest.class, org.olat.modules.adobeconnect.manager.AdobeConnectUtilsTest.class, + org.olat.modules.bigbluebutton.manager.BigBlueButtonServerDAOTest.class, org.olat.modules.bigbluebutton.manager.BigBlueButtonMeetingDAOTest.class, org.olat.modules.bigbluebutton.manager.BigBlueButtonMeetingTemplateDAOTest.class, org.olat.modules.bigbluebutton.manager.BigBlueButtonUriBuilderTest.class,