From 586ffc0c3f869e154e793ec14cfdcd8430ffb71e Mon Sep 17 00:00:00 2001 From: uhensler <urs.hensler@frentix.com> Date: Tue, 11 Aug 2020 08:34:19 +0200 Subject: [PATCH] OO-4821: BBB Opencast Recording Handler --- .../olat/modules/_spring/modulesContext.xml | 22 ++- .../bigbluebutton/BigBlueButtonManager.java | 3 +- .../BigBlueButtonRecordingsHandler.java | 3 +- .../manager/BigBlueButtonManagerImpl.java | 5 +- .../BigBlueButtonNativeRecordingsHandler.java | 3 +- ...igBlueButtonOpenCastRecordingsHandler.java | 70 ++++++- .../model/BigBlueButtonErrorCodes.java | 1 + .../ui/BigBlueButtonMeetingController.java | 6 +- .../opencast/OpencastBBBRecordingContext.java | 126 ++++++++++++ .../olat/modules/opencast/OpencastEvent.java | 40 ++++ .../olat/modules/opencast/OpencastModule.java | 180 +++++++++++++++++ .../modules/opencast/OpencastService.java | 55 ++++++ .../opencast/manager/OpencastServiceImpl.java | 75 ++++++++ .../modules/opencast/manager/client/Api.java | 52 +++++ .../opencast/manager/client/Event.java | 72 +++++++ .../manager/client/GetEventsParams.java | 67 +++++++ .../manager/client/OpencastRestClient.java | 142 ++++++++++++++ .../opencast/model/OpencastEventImpl.java | 75 ++++++++ .../opencast/ui/OpencastAdminController.java | 182 ++++++++++++++++++ .../ui/_i18n/LocalStrings_de.properties | 13 ++ .../ui/_i18n/LocalStrings_en.properties | 13 ++ .../resources/serviceconfig/olat.properties | 14 ++ .../client/OpencastRestClientTest.java | 78 ++++++++ 23 files changed, 1282 insertions(+), 15 deletions(-) create mode 100644 src/main/java/org/olat/modules/opencast/OpencastBBBRecordingContext.java create mode 100644 src/main/java/org/olat/modules/opencast/OpencastEvent.java create mode 100644 src/main/java/org/olat/modules/opencast/OpencastModule.java create mode 100644 src/main/java/org/olat/modules/opencast/OpencastService.java create mode 100644 src/main/java/org/olat/modules/opencast/manager/OpencastServiceImpl.java create mode 100644 src/main/java/org/olat/modules/opencast/manager/client/Api.java create mode 100644 src/main/java/org/olat/modules/opencast/manager/client/Event.java create mode 100644 src/main/java/org/olat/modules/opencast/manager/client/GetEventsParams.java create mode 100644 src/main/java/org/olat/modules/opencast/manager/client/OpencastRestClient.java create mode 100644 src/main/java/org/olat/modules/opencast/model/OpencastEventImpl.java create mode 100644 src/main/java/org/olat/modules/opencast/ui/OpencastAdminController.java create mode 100644 src/main/java/org/olat/modules/opencast/ui/_i18n/LocalStrings_de.properties create mode 100644 src/main/java/org/olat/modules/opencast/ui/_i18n/LocalStrings_en.properties create mode 100644 src/test/java/org/olat/modules/opencast/manager/client/OpencastRestClientTest.java diff --git a/src/main/java/org/olat/modules/_spring/modulesContext.xml b/src/main/java/org/olat/modules/_spring/modulesContext.xml index efabffe627b..51e34670314 100644 --- a/src/main/java/org/olat/modules/_spring/modulesContext.xml +++ b/src/main/java/org/olat/modules/_spring/modulesContext.xml @@ -103,7 +103,27 @@ <value>org.olat.admin.SystemAdminMainController</value> </list> </property> - </bean> + </bean> + + <!-- Opencast admin. panel --> + <bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints"> + <property name="order" value="8250" /> + <property name="actionController"> + <bean class="org.olat.core.gui.control.creator.AutoCreator" scope="prototype"> + <property name="className" value="org.olat.modules.opencast.ui.OpencastAdminController"/> + </bean> + </property> + <property name="navigationKey" value="opencast" /> + <property name="i18nActionKey" value="admin.menu.title"/> + <property name="i18nDescriptionKey" value="admin.menu.title.alt"/> + <property name="translationPackage" value="org.olat.modules.opencast.ui"/> + <property name="parentTreeNodeIdentifier" value="externalToolsParent" /> + <property name="extensionPoints"> + <list> + <value>org.olat.admin.SystemAdminMainController</value> + </list> + </property> + </bean> <!-- Goto admin. panel --> <bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints"> diff --git a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java index d37129c0892..d78caba58de 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java +++ b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java @@ -24,6 +24,7 @@ import java.util.List; import org.olat.core.id.Identity; import org.olat.core.id.Roles; +import org.olat.core.util.UserSession; import org.olat.group.BusinessGroup; import org.olat.modules.bigbluebutton.manager.BigBlueButtonUriBuilder; import org.olat.modules.bigbluebutton.model.BigBlueButtonErrors; @@ -155,7 +156,7 @@ public interface BigBlueButtonManager { public BigBlueButtonRecordingReference updateRecordingReference(BigBlueButtonRecordingReference reference); - public String getRecordingUrl(BigBlueButtonRecording record); + public String getRecordingUrl(UserSession usess, BigBlueButtonRecording record); public void deleteRecording(BigBlueButtonRecording record, BigBlueButtonMeeting meeting, BigBlueButtonErrors errors); diff --git a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonRecordingsHandler.java b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonRecordingsHandler.java index 52a224306b9..5483cd4b9ca 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonRecordingsHandler.java +++ b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonRecordingsHandler.java @@ -22,6 +22,7 @@ package org.olat.modules.bigbluebutton; import java.util.List; import java.util.Locale; +import org.olat.core.util.UserSession; import org.olat.modules.bigbluebutton.manager.BigBlueButtonUriBuilder; import org.olat.modules.bigbluebutton.model.BigBlueButtonErrors; @@ -41,7 +42,7 @@ public interface BigBlueButtonRecordingsHandler { public List<BigBlueButtonRecording> getRecordings(BigBlueButtonMeeting meeting, BigBlueButtonErrors errors); - public String getRecordingURL(BigBlueButtonRecording recording); + public String getRecordingURL(UserSession usess, BigBlueButtonRecording recording); public void appendMetadata(BigBlueButtonUriBuilder uriBuilder, BigBlueButtonMeeting meeting); 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 ae5e6fe68fd..43cd249ba18 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java +++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java @@ -53,6 +53,7 @@ import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.logging.Tracing; import org.olat.core.util.CodeHelper; import org.olat.core.util.StringHelper; +import org.olat.core.util.UserSession; import org.olat.core.util.WebappHelper; import org.olat.course.CourseFactory; import org.olat.course.ICourse; @@ -921,8 +922,8 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager, } @Override - public String getRecordingUrl(BigBlueButtonRecording recording) { - return getRecordingsHandler().getRecordingURL(recording); + public String getRecordingUrl(UserSession usess, BigBlueButtonRecording recording) { + return getRecordingsHandler().getRecordingURL(usess, recording); } @Override diff --git a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonNativeRecordingsHandler.java b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonNativeRecordingsHandler.java index c818c0dd9aa..34ffc1384b0 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonNativeRecordingsHandler.java +++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonNativeRecordingsHandler.java @@ -31,6 +31,7 @@ import org.olat.core.id.User; import org.olat.core.logging.Tracing; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; +import org.olat.core.util.UserSession; import org.olat.core.util.Util; import org.olat.group.BusinessGroup; import org.olat.modules.bigbluebutton.BigBlueButtonManager; @@ -99,7 +100,7 @@ public class BigBlueButtonNativeRecordingsHandler implements BigBlueButtonRecord } @Override - public String getRecordingURL(BigBlueButtonRecording recording) { + public String getRecordingURL(UserSession usess, BigBlueButtonRecording recording) { return recording.getUrl(); } diff --git a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonOpenCastRecordingsHandler.java b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonOpenCastRecordingsHandler.java index a9398c18e5d..ae94c72eec0 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonOpenCastRecordingsHandler.java +++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonOpenCastRecordingsHandler.java @@ -19,27 +19,46 @@ */ package org.olat.modules.bigbluebutton.manager; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Map; +import org.apache.logging.log4j.Logger; +import org.olat.core.dispatcher.mapper.Mapper; +import org.olat.core.dispatcher.mapper.MapperService; +import org.olat.core.dispatcher.mapper.manager.MapperKey; import org.olat.core.gui.translator.Translator; import org.olat.core.id.User; import org.olat.core.id.UserConstants; +import org.olat.core.logging.Tracing; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; +import org.olat.core.util.UserSession; import org.olat.core.util.Util; import org.olat.course.CourseFactory; import org.olat.course.ICourse; import org.olat.course.nodes.CourseNode; import org.olat.group.BusinessGroup; +import org.olat.ims.lti.LTIContext; +import org.olat.ims.lti.LTIManager; +import org.olat.ims.lti.ui.PostDataMapper; import org.olat.modules.bigbluebutton.BigBlueButtonMeeting; import org.olat.modules.bigbluebutton.BigBlueButtonRecording; import org.olat.modules.bigbluebutton.BigBlueButtonRecordingsHandler; +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.BigBlueButtonRecordingImpl; import org.olat.modules.bigbluebutton.ui.BigBlueButtonAdminController; +import org.olat.modules.opencast.OpencastBBBRecordingContext; +import org.olat.modules.opencast.OpencastEvent; +import org.olat.modules.opencast.OpencastModule; +import org.olat.modules.opencast.OpencastService; import org.olat.repository.RepositoryEntry; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @@ -51,10 +70,21 @@ import org.springframework.stereotype.Service; */ @Service @Qualifier("opencast") -public class BigBlueButtonOpenCastRecordingsHandler implements BigBlueButtonRecordingsHandler { +public class BigBlueButtonOpenCastRecordingsHandler implements BigBlueButtonRecordingsHandler { + + private static final Logger log = Tracing.createLoggerFor(BigBlueButtonOpenCastRecordingsHandler.class); public static final String OPENCAST_RECORDING_HANDLER_ID = "opencast"; + @Autowired + private OpencastModule opencastModule; + @Autowired + private OpencastService opencastService; + @Autowired + private LTIManager ltiManager; + @Autowired + private MapperService mapperService; + @Override public String getId() { return OPENCAST_RECORDING_HANDLER_ID; @@ -73,12 +103,34 @@ public class BigBlueButtonOpenCastRecordingsHandler implements BigBlueButtonReco @Override public List<BigBlueButtonRecording> getRecordings(BigBlueButtonMeeting meeting, BigBlueButtonErrors errors) { - return Collections.emptyList(); + if(!opencastModule.isEnabled()) { + log.error("Try getting recordings of disabled Opencast: {}", opencastModule.getApiUrl()); + errors.append(new BigBlueButtonError(BigBlueButtonErrorCodes.opencastDisabled)); + return Collections.emptyList(); + } + + List<OpencastEvent> events = opencastService.getEvents(meeting.getIdentifier()); + List<BigBlueButtonRecording> recordings = new ArrayList<>(events.size()); + for (OpencastEvent event : events) { + String recordId = event.getIdentifier(); + String name = event.getTitle(); + String meetingId = meeting.getIdentifier(); + Date startTime = event.getStart(); + Date endTime = event.getEnd(); + String url = null; + String type = null; + recordings.add(BigBlueButtonRecordingImpl.valueOf(recordId, name, meetingId, startTime, endTime, url, type)); + } + return recordings; } @Override - public String getRecordingURL(BigBlueButtonRecording recording) { - return recording.getUrl(); + public String getRecordingURL(UserSession usess, BigBlueButtonRecording recording) { + LTIContext context = new OpencastBBBRecordingContext(recording.getRecordId()); + Map<String,String> unsignedProps = ltiManager.forgeLTIProperties(usess.getIdentity(), usess.getLocale(), context, false, false, true); + Mapper contentMapper = new PostDataMapper(unsignedProps, opencastModule.getLtiUrl(), opencastModule.getLtiKey(), opencastModule.getLtiSecret(), false); + MapperKey mapperKey = mapperService.register(usess, contentMapper); + return mapperKey.getUrl(); } @Override @@ -139,7 +191,7 @@ public class BigBlueButtonOpenCastRecordingsHandler implements BigBlueButtonReco // Location of the event uriBuilder.optionalParameter("meta_dc-spatial", "Olat-BigBlueButton"); // Date of the event - uriBuilder.optionalParameter("meta_dc-created", Formatter.formatDatetime(meetingCreation)); + uriBuilder.optionalParameter("meta_dc-created", Formatter.formatDatetime(meetingCreation)); uriBuilder.optionalParameter("meta_opencast-series-dc-title", seriesTitle); } @@ -158,6 +210,12 @@ public class BigBlueButtonOpenCastRecordingsHandler implements BigBlueButtonReco @Override public boolean deleteRecordings(List<BigBlueButtonRecording> recordings, BigBlueButtonMeeting meeting, BigBlueButtonErrors errors) { - return false; + if (!opencastModule.isEnabled()) { + log.error("Try deleting a recording of disabled Opencast: {}", opencastModule.getApiUrl()); + errors.append(new BigBlueButtonError(BigBlueButtonErrorCodes.opencastDisabled)); + return false; + } + + return opencastService.deleteEvents(meeting.getIdentifier()); } } diff --git a/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonErrorCodes.java b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonErrorCodes.java index f40b7471488..919d7cdf675 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonErrorCodes.java +++ b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonErrorCodes.java @@ -31,6 +31,7 @@ public enum BigBlueButtonErrorCodes { deletedObject, serverNotAvailable, serverDisabled, + opencastDisabled, unkown ; diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingController.java index cb3061d1bc7..df1e6d4def0 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingController.java +++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingController.java @@ -381,7 +381,7 @@ public class BigBlueButtonMeetingController extends FormBasicController implemen if("delete".equals(se.getCommand())) { doConfirmDeleteRecording(ureq, recordingTableModel.getObject(se.getIndex()).getRecording()); } else if("open-recording".equals(se.getCommand())) { - doOpenRecording(recordingTableModel.getObject(se.getIndex()).getRecording()); + doOpenRecording(ureq, recordingTableModel.getObject(se.getIndex()).getRecording()); } } } else if(source instanceof FormLink) { @@ -442,8 +442,8 @@ public class BigBlueButtonMeetingController extends FormBasicController implemen } } - private void doOpenRecording(BigBlueButtonRecording recording) { - String url = bigBlueButtonManager.getRecordingUrl(recording); + private void doOpenRecording(UserRequest ureq, BigBlueButtonRecording recording) { + String url = bigBlueButtonManager.getRecordingUrl(ureq.getUserSession(), recording); if(StringHelper.containsNonWhitespace(url)) { getWindowControl().getWindowBackOffice().sendCommandTo(CommandFactory.createNewWindowRedirectTo(url)); } else { diff --git a/src/main/java/org/olat/modules/opencast/OpencastBBBRecordingContext.java b/src/main/java/org/olat/modules/opencast/OpencastBBBRecordingContext.java new file mode 100644 index 00000000000..e6d53e3fc98 --- /dev/null +++ b/src/main/java/org/olat/modules/opencast/OpencastBBBRecordingContext.java @@ -0,0 +1,126 @@ +/** + * <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.opencast; + +import java.util.HashMap; +import java.util.Map; + +import org.olat.basesecurity.BaseSecurityManager; +import org.olat.core.CoreSpringFactory; +import org.olat.core.id.Identity; +import org.olat.ims.lti.LTIContext; +import org.olat.ims.lti.LTIDisplayOptions; +import org.olat.ims.lti.LTIManager; + +/** + * + * Initial date: 10.09.2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class OpencastBBBRecordingContext implements LTIContext { + + private static final String LTI_ROLE = "Learner"; + private static final String HEIGTH = "auto"; + private static final String WIDTH = "auto"; + private static final String TARGET = LTIDisplayOptions.fullscreen.name(); + private static final String CUSTOM_TOOL = "tool"; + + private final String identifier; + + public OpencastBBBRecordingContext(String identifier) { + this.identifier = identifier; + } + + @Override + public String getSourcedId() { + return null; + } + + @Override + public String getTalkBackMapperUri() { + return null; + } + + @Override + public String getOutcomeMapperUri() { + return null; + } + + @Override + public String getResourceId() { + return null; + } + + @Override + public String getResourceTitle() { + return null; + } + + @Override + public String getResourceDescription() { + return null; + } + + @Override + public String getContextId() { + return null; + } + + @Override + public String getContextTitle() { + return null; + } + + @Override + public String getRoles(Identity identity) { + return LTI_ROLE; + } + + @Override + public String getCustomProperties() { + Map<String, String> customProps = new HashMap<>(); + + customProps.put(CUSTOM_TOOL, "play/" + identifier); + + return CoreSpringFactory.getImpl(LTIManager.class).joinCustomProps(customProps); + } + + @Override + public String getTarget() { + return TARGET; + } + + @Override + public String getPreferredWidth() { + return WIDTH; + } + + @Override + public String getPreferredHeight() { + return HEIGTH; + } + + @Override + public String getUserId(Identity identity) { + return CoreSpringFactory.getImpl(BaseSecurityManager.class).findAuthenticationName(identity); + } + +} diff --git a/src/main/java/org/olat/modules/opencast/OpencastEvent.java b/src/main/java/org/olat/modules/opencast/OpencastEvent.java new file mode 100644 index 00000000000..4348e57421c --- /dev/null +++ b/src/main/java/org/olat/modules/opencast/OpencastEvent.java @@ -0,0 +1,40 @@ +/** + * <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.opencast; + +import java.util.Date; + +/** + * + * Initial date: 10 Aug 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public interface OpencastEvent { + + String getIdentifier(); + + String getTitle(); + + Date getStart(); + + Date getEnd(); + +} diff --git a/src/main/java/org/olat/modules/opencast/OpencastModule.java b/src/main/java/org/olat/modules/opencast/OpencastModule.java new file mode 100644 index 00000000000..4f6bf53795b --- /dev/null +++ b/src/main/java/org/olat/modules/opencast/OpencastModule.java @@ -0,0 +1,180 @@ +/** + * <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.opencast; + +import java.nio.charset.StandardCharsets; + +import org.apache.commons.codec.binary.Base64; +import org.apache.logging.log4j.Logger; +import org.olat.core.configuration.AbstractSpringModule; +import org.olat.core.configuration.ConfigOnOff; +import org.olat.core.logging.Tracing; +import org.olat.core.util.StringHelper; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +/** + * + * Initial date: 4 Aug 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +@Service +public class OpencastModule extends AbstractSpringModule implements ConfigOnOff { + + private static final Logger log = Tracing.createLoggerFor(OpencastModule.class); + + private static final String ENABLED = "opencast.enabled"; + private static final String API_URL = "api.url"; + private static final String API_USERNAME = "api.username"; + private static final String API_PASSOWRD = "api.password"; + private static final String LTI_URL = "lti.url"; + private static final String LTI_KEY = "lti.key"; + private static final String LTI_SECRET = "lti.secret"; + + @Value("${opencast.enabled}") + private boolean enabled; + @Value("${opencast.api.url}") + private String apiUrl; + @Value("${opencast.api.username}") + private String apiUsername; + @Value("${opencast.api.password}") + private String apiPassword; + @Value("${opencast.lti.url}") + private String ltiUrl; + @Value("${opencast.lti.key}") + private String ltiKey; + @Value("${opencast.lti.secret}") + private String ltiSecret; + + private String apiAuthorizationHeader; + + @Autowired + public OpencastModule(CoordinatorManager coordinatorManager) { + super(coordinatorManager); + } + + @Override + public void init() { + String enabledObj = getStringPropertyValue(ENABLED, true); + if(StringHelper.containsNonWhitespace(enabledObj)) { + enabled = "true".equals(enabledObj); + } + + apiUrl = getStringPropertyValue(API_URL, apiUrl); + apiUsername = getStringPropertyValue(API_USERNAME, apiUsername); + apiPassword = getStringPropertyValue(API_PASSOWRD, apiPassword); + refreshApiAutorization(); + + ltiUrl = getStringPropertyValue(LTI_URL, ltiUrl); + ltiKey = getStringPropertyValue(LTI_KEY, ltiKey); + ltiSecret = getStringPropertyValue(LTI_SECRET, ltiSecret); + } + + @Override + protected void initFromChangedProperties() { + init(); + } + + @Override + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + setStringProperty(ENABLED, Boolean.toString(enabled), true); + } + + public void setApiUrl(String apiUrl) { + this.apiUrl = apiUrl; + setStringProperty(API_URL, apiUrl, true); + } + + public String getApiUrl() { + return apiUrl; + } + + public String getApiUsername() { + return apiUsername; + } + + public String getApiPassword() { + return apiPassword; + } + + public void setApiCredentials(String apiUsername, String apiPassword) { + this.apiUsername = apiUsername; + setStringProperty(API_USERNAME, apiUsername, true); + + this.apiPassword = apiPassword; + setSecretStringProperty(API_PASSOWRD, apiPassword, true); + + refreshApiAutorization(); + } + + /* + * Did not work with BasicCredentialsProvider!? + * So let's create the AUTHORIZATION header by ourself. + */ + private void refreshApiAutorization() { + try { + String auth = apiUsername + ":" + apiPassword; + byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.ISO_8859_1)); + apiAuthorizationHeader = "Basic " + new String(encodedAuth); + } catch (Exception e) { + log.error("Opencast AUTHORIZATION header not created", e); + } + } + + public String getApiAuthorizationHeader() { + return apiAuthorizationHeader; + } + + public String getLtiUrl() { + return ltiUrl; + } + + public void setLtiUrl(String ltiUrl) { + this.ltiUrl = ltiUrl; + setStringProperty(LTI_URL, ltiUrl, true); + } + + public String getLtiKey() { + return ltiKey; + } + + public void setLtiKey(String ltiKey) { + this.ltiKey = ltiKey; + setStringProperty(LTI_KEY, ltiKey, true); + } + + public String getLtiSecret() { + return ltiSecret; + } + + public void setLtiSecret(String ltiSecret) { + this.ltiSecret = ltiSecret; + setStringProperty(LTI_SECRET, ltiSecret, true); + } + +} diff --git a/src/main/java/org/olat/modules/opencast/OpencastService.java b/src/main/java/org/olat/modules/opencast/OpencastService.java new file mode 100644 index 00000000000..5f0cbca8595 --- /dev/null +++ b/src/main/java/org/olat/modules/opencast/OpencastService.java @@ -0,0 +1,55 @@ +/** + * <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.opencast; + +import java.util.List; + +/** + * + * Initial date: 4 Aug 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public interface OpencastService { + + /** + * Check if the connection to Opencast can be established with the url and credentials of the OpencastModul. + * + * @return true if the connection was successfully established. + */ + boolean checkApiConnection(); + + /** + * Get the events with the identifier + * + * @param identifier + * @return + */ + List<OpencastEvent> getEvents(String identifier); + + /** + * Delete all events with the identifier. + * + * @param identifier + * @return true if some event was deleted + */ + boolean deleteEvents(String identifier); + +} diff --git a/src/main/java/org/olat/modules/opencast/manager/OpencastServiceImpl.java b/src/main/java/org/olat/modules/opencast/manager/OpencastServiceImpl.java new file mode 100644 index 00000000000..3ff89d32959 --- /dev/null +++ b/src/main/java/org/olat/modules/opencast/manager/OpencastServiceImpl.java @@ -0,0 +1,75 @@ +/** + * <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.opencast.manager; + +import java.util.ArrayList; +import java.util.List; + +import org.olat.modules.opencast.OpencastEvent; +import org.olat.modules.opencast.OpencastService; +import org.olat.modules.opencast.manager.client.Api; +import org.olat.modules.opencast.manager.client.Event; +import org.olat.modules.opencast.manager.client.GetEventsParams; +import org.olat.modules.opencast.manager.client.OpencastRestClient; +import org.olat.modules.opencast.model.OpencastEventImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * + * Initial date: 4 Aug 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +@Service +public class OpencastServiceImpl implements OpencastService { + + @Autowired + private OpencastRestClient opencastRestClient; + + @Override + public boolean checkApiConnection() { + Api api = opencastRestClient.getApi(); + return api != null && api.getVersion() != null; + } + + @Override + public List<OpencastEvent> getEvents(String identifier) { + GetEventsParams params = new GetEventsParams(); + params.getFilter().setTextFilter(identifier); + Event[] events = opencastRestClient.getEvents(params); + List<OpencastEvent> opencastEvents = new ArrayList<>(events.length); + for (Event event : events) { + OpencastEventImpl opencastEvent = new OpencastEventImpl(); + opencastEvent.setIdentifier(event.getIdentifier()); + opencastEvent.setTitle(event.getTitle()); + opencastEvent.setStart(event.getStart()); + // End has to be calculated with the duration, but the duration of the event is always 0. + // Only the duration of the metadata would be the right value. We skip that for now. + opencastEvents.add(opencastEvent); + } + return opencastEvents; + } + + @Override + public boolean deleteEvents(String identifier) { + return opencastRestClient.deleteEvent(identifier); + } +} diff --git a/src/main/java/org/olat/modules/opencast/manager/client/Api.java b/src/main/java/org/olat/modules/opencast/manager/client/Api.java new file mode 100644 index 00000000000..660f3af1cdb --- /dev/null +++ b/src/main/java/org/olat/modules/opencast/manager/client/Api.java @@ -0,0 +1,52 @@ +/** + * <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.opencast.manager.client; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * + * Initial date: 4 Aug 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +@JsonIgnoreProperties(ignoreUnknown=true) +public class Api { + + private String version; + private String url; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + +} diff --git a/src/main/java/org/olat/modules/opencast/manager/client/Event.java b/src/main/java/org/olat/modules/opencast/manager/client/Event.java new file mode 100644 index 00000000000..62e83a213df --- /dev/null +++ b/src/main/java/org/olat/modules/opencast/manager/client/Event.java @@ -0,0 +1,72 @@ +/** + * <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.opencast.manager.client; + +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * + * Initial date: 10 Aug 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +@JsonIgnoreProperties(ignoreUnknown=true) +public class Event { + + private String identifier; + private String title; + private Date start; + private String duration; + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Date getStart() { + return start; + } + + public void setStart(Date start) { + this.start = start; + } + + public String getDuration() { + return duration; + } + + public void setDuration(String duration) { + this.duration = duration; + } + +} diff --git a/src/main/java/org/olat/modules/opencast/manager/client/GetEventsParams.java b/src/main/java/org/olat/modules/opencast/manager/client/GetEventsParams.java new file mode 100644 index 00000000000..97d35453d3d --- /dev/null +++ b/src/main/java/org/olat/modules/opencast/manager/client/GetEventsParams.java @@ -0,0 +1,67 @@ +/** + * <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.opencast.manager.client; + +import org.olat.core.util.StringHelper; + +/** + * + * Initial date: 10 Aug 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class GetEventsParams { + + private Filter filter; + + public Filter getFilter() { + if (filter == null) { + filter = new Filter(); + } + return filter; + } + + public String getFilterParam() { + if (filter != null) { + StringBuilder sb = new StringBuilder(); + String textFilter = filter.getTextFilter(); + if (StringHelper.containsNonWhitespace(textFilter)) { + sb.append("textFilter:").append(textFilter); + } + return sb.toString(); + } + return null; + } + + public static final class Filter { + + private String textFilter; + + private String getTextFilter() { + return textFilter; + } + + public void setTextFilter(String textFilter) { + this.textFilter = textFilter; + } + + } + +} diff --git a/src/main/java/org/olat/modules/opencast/manager/client/OpencastRestClient.java b/src/main/java/org/olat/modules/opencast/manager/client/OpencastRestClient.java new file mode 100644 index 00000000000..b28dff3dbb5 --- /dev/null +++ b/src/main/java/org/olat/modules/opencast/manager/client/OpencastRestClient.java @@ -0,0 +1,142 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.opencast.manager.client; + +import java.net.URI; + +import org.apache.http.HttpHeaders; +import org.apache.http.HttpStatus; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; +import org.apache.logging.log4j.Logger; +import org.olat.core.logging.Tracing; +import org.olat.core.util.StringHelper; +import org.olat.modules.opencast.OpencastModule; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * + * Initial date: 4 Aug 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +@Service +public class OpencastRestClient { + + private static final Logger log = Tracing.createLoggerFor(OpencastRestClient.class); + + private static final Event[] NO_EVENTS = new Event[]{}; + private static final int TIMEOUT_5000_MILLIS = 5000; + private static final RequestConfig REQUEST_CONFIG = RequestConfig.copy(RequestConfig.DEFAULT) + .setSocketTimeout(TIMEOUT_5000_MILLIS) + .setConnectTimeout(TIMEOUT_5000_MILLIS) + .setConnectionRequestTimeout(TIMEOUT_5000_MILLIS) + .build(); + + @Autowired + private OpencastModule opencastModule; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + public Api getApi() { + URI uri = URI.create(opencastModule.getApiUrl()); + HttpGet request = new HttpGet(uri); + decorateRequest(request); + + try(CloseableHttpClient client = HttpClientBuilder.create().build(); + CloseableHttpResponse response = client.execute(request)) { + int statusCode = response.getStatusLine().getStatusCode(); + log.debug("Status code of: {} {}", uri, statusCode); + + if (statusCode == HttpStatus.SC_OK) { + String json = EntityUtils.toString(response.getEntity(), "UTF-8"); + return objectMapper.readValue(json, Api.class); + } + } catch(Exception e) { + log.error("Cannot send: {}", uri, e); + } + return null; + } + + public Event[] getEvents(GetEventsParams params) { + URI uri; + try { + URIBuilder builder = new URIBuilder(opencastModule.getApiUrl() + "/events"); + String filterParam = params.getFilterParam(); + if (StringHelper.containsNonWhitespace(filterParam)) { + builder.addParameter("filter", filterParam); + } + uri = builder.build(); + } catch (Exception e) { + log.error("Cannot get Opencast events.", e); + return NO_EVENTS; + } + + HttpGet request = new HttpGet(uri); + decorateRequest(request); + + try(CloseableHttpClient client = HttpClientBuilder.create().build(); + CloseableHttpResponse response = client.execute(request)) { + int statusCode = response.getStatusLine().getStatusCode(); + log.debug("Status code of: {} {}", uri, statusCode); + if (statusCode == HttpStatus.SC_OK) { + String json = EntityUtils.toString(response.getEntity(), "UTF-8"); + return objectMapper.readValue(json, Event[].class); + } + } catch(Exception e) { + log.error("Cannot send: {}", uri, e); + } + return NO_EVENTS; + } + + public boolean deleteEvent(String identifier) { + URI uri = URI.create(opencastModule.getApiUrl() + "/events/" + identifier); + HttpDelete request = new HttpDelete(uri); + decorateRequest(request); + + try(CloseableHttpClient client = HttpClientBuilder.create().build(); + CloseableHttpResponse response = client.execute(request)) { + int statusCode = response.getStatusLine().getStatusCode(); + log.debug("Status code of: {} {}", uri, statusCode); + if (statusCode == HttpStatus.SC_NO_CONTENT || statusCode == HttpStatus.SC_OK) { + return true; + } + } catch(Exception e) { + log.error("Cannot send: {}", uri, e); + } + return false; + } + + private void decorateRequest(HttpRequestBase request) { + request.setConfig(REQUEST_CONFIG); + request.setHeader(HttpHeaders.AUTHORIZATION, opencastModule.getApiAuthorizationHeader()); + } + +} diff --git a/src/main/java/org/olat/modules/opencast/model/OpencastEventImpl.java b/src/main/java/org/olat/modules/opencast/model/OpencastEventImpl.java new file mode 100644 index 00000000000..e6fb476a404 --- /dev/null +++ b/src/main/java/org/olat/modules/opencast/model/OpencastEventImpl.java @@ -0,0 +1,75 @@ +/** + * <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.opencast.model; + +import java.util.Date; + +import org.olat.modules.opencast.OpencastEvent; + +/** + * + * Initial date: 10 Aug 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class OpencastEventImpl implements OpencastEvent { + + private String identifier; + private String title; + private Date start; + private Date end; + + @Override + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + @Override + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + @Override + public Date getStart() { + return start; + } + + public void setStart(Date start) { + this.start = start; + } + + @Override + public Date getEnd() { + return end; + } + + public void setEnd(Date end) { + this.end = end; + } + +} diff --git a/src/main/java/org/olat/modules/opencast/ui/OpencastAdminController.java b/src/main/java/org/olat/modules/opencast/ui/OpencastAdminController.java new file mode 100644 index 00000000000..eb1a4a91927 --- /dev/null +++ b/src/main/java/org/olat/modules/opencast/ui/OpencastAdminController.java @@ -0,0 +1,182 @@ +/** + * <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.opencast.ui; + +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.WindowControl; +import org.olat.core.util.StringHelper; +import org.olat.modules.opencast.OpencastModule; +import org.olat.modules.opencast.OpencastService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 4 Aug 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class OpencastAdminController extends FormBasicController { + + private static final String[] ENABLED_KEYS = new String[]{ "on" }; + + private MultipleSelectionElement enabledEl; + private TextElement apiUrlEl; + private TextElement apiUsernameEl; + private TextElement apiPasswordEl; + private TextElement ltiUrlEl; + private TextElement ltiKeyEl; + private TextElement ltiSectretEl; + private FormLink checkApiConnectionButton; + + @Autowired + private OpencastModule opencastModule; + @Autowired + private OpencastService opencastService; + + public OpencastAdminController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormTitle("admin.title"); + + String[] enableValues = new String[]{ translate("on") }; + enabledEl = uifactory.addCheckboxesHorizontal("admin.enabled", formLayout, ENABLED_KEYS, enableValues); + enabledEl.select(ENABLED_KEYS[0], opencastModule.isEnabled()); + + String apiUrl = opencastModule.getApiUrl(); + apiUrlEl = uifactory.addTextElement("admin.api.url", "admin.api.url", 128, apiUrl, formLayout); + apiUrlEl.setMandatory(true); + + String apiUsername = opencastModule.getApiUsername(); + apiUsernameEl = uifactory.addTextElement("admin.api.username", 128, apiUsername, formLayout); + apiUsernameEl.setMandatory(true); + + String apiPassword = opencastModule.getApiPassword(); + apiPasswordEl = uifactory.addPasswordElement("admin.api.password", "admin.api.password", 128, apiPassword, formLayout); + apiPasswordEl.setAutocomplete("new-password"); + apiPasswordEl.setMandatory(true); + + String ltiUrl = opencastModule.getApiUrl(); + ltiUrlEl = uifactory.addTextElement("admin.lti.url", "admin.lti.url", 128, ltiUrl, formLayout); + ltiUrlEl.setMandatory(true); + + String ltiKey = opencastModule.getLtiKey(); + ltiKeyEl = uifactory.addTextElement("admin.lti.key", 123, ltiKey, formLayout); + ltiKeyEl.setMandatory(true); + + String ltiSecret = opencastModule.getLtiSecret(); + ltiSectretEl = uifactory.addPasswordElement("admin.lti.secret", "admin.lti.secret", 128, ltiSecret, formLayout); + ltiSectretEl.setAutocomplete("new-password"); + ltiSectretEl.setMandatory(true); + + FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + formLayout.add("buttons", buttonLayout); + uifactory.addFormSubmitButton("save", buttonLayout); + checkApiConnectionButton = uifactory.addFormLink("admin.check.api.connection", buttonLayout, Link.BUTTON); + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + boolean allOk = true; + + //validate only if the module is enabled + if(enabledEl.isAtLeastSelected(1)) { + allOk &= validateIsMandatory(apiUrlEl); + allOk &= validateIsMandatory(apiUsernameEl); + allOk &= validateIsMandatory(apiPasswordEl); + allOk &= validateIsMandatory(ltiUrlEl); + allOk &= validateIsMandatory(ltiKeyEl); + allOk &= validateIsMandatory(ltiSectretEl); + } + + return allOk & super.validateFormLogic(ureq); + } + + private boolean validateIsMandatory(TextElement textElement) { + boolean allOk = true; + + if (!StringHelper.containsNonWhitespace(textElement.getValue())) { + textElement.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + + return allOk; + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if (source == checkApiConnectionButton) { + doCheckApiConnection(); + } + super.formInnerEvent(ureq, source, event); + } + + @Override + protected void formOK(UserRequest ureq) { + boolean enabled = enabledEl.isAtLeastSelected(1); + opencastModule.setEnabled(enabled); + + String apiUrl = apiUrlEl.getValue(); + apiUrl = apiUrl.endsWith("/")? apiUrl.substring(0, apiUrl.length() - 1): apiUrl; + opencastModule.setApiUrl(apiUrl); + + String apiUsername = apiUsernameEl.getValue(); + String apiPassword = apiPasswordEl.getValue(); + opencastModule.setApiCredentials(apiUsername, apiPassword); + + String ltiUrl = ltiUrlEl.getValue(); + ltiUrl = ltiUrl.endsWith("/")? ltiUrl.substring(0, ltiUrl.length() - 1): ltiUrl; + opencastModule.setLtiUrl(ltiUrl); + + String ltiKey = ltiKeyEl.getValue(); + opencastModule.setLtiKey(ltiKey); + + String ltiSecret = ltiSectretEl.getValue(); + opencastModule.setLtiSecret(ltiSecret); + } + + private void doCheckApiConnection() { + boolean connectionOk = opencastService.checkApiConnection(); + if (connectionOk) { + showInfo("check.api.connection.ok"); + } else { + showError("check.api.connection.nok"); + } + } + + @Override + protected void doDispose() { + // + } + +} diff --git a/src/main/java/org/olat/modules/opencast/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/opencast/ui/_i18n/LocalStrings_de.properties new file mode 100644 index 00000000000..b4a3e84df20 --- /dev/null +++ b/src/main/java/org/olat/modules/opencast/ui/_i18n/LocalStrings_de.properties @@ -0,0 +1,13 @@ +admin.api.password=API Passwort +admin.api.url=API URL +admin.api.username=API Benutzername +admin.check.api.connection=API Verbindung testen +admin.enabled=Modul "Opencast" +admin.lti.key=LTI Key +admin.lti.secret=LTI Secret +admin.api.url=LTI URL +admin.menu.title=Opencast +admin.menu.title.alt=Opencast +admin.title=Konfiguration +check.api.connection.ok=Die Verbindung konnte erfolgreich hergestellt werden! +check.api.connection.nok=Die Verbindung konnte nicht hergestellt werden! \ No newline at end of file diff --git a/src/main/java/org/olat/modules/opencast/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/opencast/ui/_i18n/LocalStrings_en.properties new file mode 100644 index 00000000000..a319be2c0ce --- /dev/null +++ b/src/main/java/org/olat/modules/opencast/ui/_i18n/LocalStrings_en.properties @@ -0,0 +1,13 @@ +admin.api.password=API Password +admin.api.username=API Username +admin.api.url=API URL +admin.check.api.connection=Check API connection +admin.enabled=Module "Opencast" +admin.lti.url=LTI URL +admin.lti.key=LTI Key +admin.lti.secret=LTI Secret +admin.menu.title=Opencast +admin.menu.title.alt=Opencast +admin.title=Configuration +check.api.connection.ok=The connection was successfully established. +check.api.connection.nok=The connection could not be established! \ No newline at end of file diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties index 76e6f2bb0e1..3611f88bd37 100644 --- a/src/main/resources/serviceconfig/olat.properties +++ b/src/main/resources/serviceconfig/olat.properties @@ -1757,6 +1757,20 @@ video.transcoding.dir.values=${folder.root}/transcodedVideos, /mount/cheap/disk/ # allow to retrieve metadata for youtube video youtube.api.key= +############################################################################### +# Opencast +############################################################################### +opencast.enabled=false +# API +opencast.api.url=https://opencast.example.com/api +# Username and password of the technical opencast user +opencast.api.username= +opencast.api.password= +# LTI +opencast.lti.url=https://opencast.example.com/lti +opencast.lti.key= +opencast.lti.secret= + ############################################################################### # Options for the live stream course node ############################################################################### diff --git a/src/test/java/org/olat/modules/opencast/manager/client/OpencastRestClientTest.java b/src/test/java/org/olat/modules/opencast/manager/client/OpencastRestClientTest.java new file mode 100644 index 00000000000..d7ec2939617 --- /dev/null +++ b/src/test/java/org/olat/modules/opencast/manager/client/OpencastRestClientTest.java @@ -0,0 +1,78 @@ +/** + * <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.opencast.manager.client; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.assertj.core.api.SoftAssertions; +import org.junit.Before; +import org.junit.Test; +import org.olat.modules.opencast.OpencastModule; +import org.olat.test.OlatTestCase; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * This tests are run again a real Opencast instance. + * CAUTION: Do not run the thest agians a productive Opencast instance! + * Do not add this file to AllTestsJUnit4. You should only run it manually. + * + * Initial date: 5 Aug 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class OpencastRestClientTest extends OlatTestCase { + + @Autowired + private OpencastModule opencastModule; + + @Autowired + private OpencastRestClient sut; + + @Before + public void setUp() { + opencastModule.setApiUrl("http://localhost:8480"); + opencastModule.setApiCredentials("admin", "opencast"); + } + + @Test + public void shouldGetApi() { + Api api = sut.getApi(); + + assertThat(api).isNotNull(); + } + + @Test + public void shouldGetEvents() { + GetEventsParams params = new GetEventsParams(); + params.getFilter().setTextFilter("8678c09a-9f76-4d60-b178-fb84d2b9c494"); + + Event[] events = sut.getEvents(params); + + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(events.length).isEqualTo(1); + Event event = events[0]; + softly.assertThat(event.getIdentifier()).isNotNull(); + softly.assertThat(event.getTitle()).isNotNull(); + softly.assertThat(event.getStart()).isNotNull(); + softly.assertThat(event.getDuration()).isNotNull(); + softly.assertAll(); + } + +} -- GitLab