- unit test passing again

This commit is contained in:
Richard Alam 2011-06-23 14:04:00 -04:00
parent e9bd55a563
commit 8c8e009baa
15 changed files with 684 additions and 742 deletions

View File

@ -84,7 +84,7 @@ public class RedisListener{
String meetingId = map.get("meetingId");
String request = map.get("request");
if(request != null){
if(request.equalsIgnoreCase("end")){
if(request.equalsIgnoreCase("endMeeting")){
roomsManager.endMeetingRequest(meetingId);
}
}

View File

@ -79,39 +79,49 @@ bigbluebutton.web.logoutURL=
# successfully joining the meeting.
defaultClientUrl=${bigbluebutton.web.serverURL}/client/BigBlueButton.html
apiVersion=0.7
# The number of minutes before the system removed the meeting from memory.
defaultMeetingExpireDuration=60
# Salt which is used by 3rd-party apps to authenticate api calls
securitySalt=e49e0123e531d0816abaf4bc1b1d7f11
# Directory where we drop the <meeting-id-recorded>.done file
recordStatusDir=/var/bigbluebutton/recording/status/recorded
redisHost=localhost
redisPort=6379
# The directory where the published/unpublised recordings are located. This is for
# the get recording* api calls
publishedDir=/var/bigbluebutton/published
unpublishedDir=/var/bigbluebutton/unpublished
# If the API is enabled.
serviceEnabled = true
# Test voiceBridge number
testVoiceBridge=99999
testConferenceMock=conference-mock-default
#------------------------------------------------------
# These properties are used to test the conversion process.
# Conference name folder in ${presentationDir} (see above)
beans.presentationService.testConferenceMock=conference-mock-default
beans.dynamicConferenceService.testConferenceMock=conference-mock-default
beans.presentationService.testConferenceMock=${testConferenceMock}
# Conference room folder in ${presentationDir}/${testConferenceMock}
beans.presentationService.testRoomMock=conference-mock-default
# Uploaded presentation name
beans.presentationService.testPresentationName=appkonference
# Uploaded presentation file
beans.presentationService.testUploadedPresentation=appkonference.txt
# Test voiceBridge number
beans.dynamicConferenceService.testVoiceBridge=99999
redisHost=localhost
redisPort=6379
# Directory where we drop the <meeting-id-recorded>.done file
beans.dynamicConferenceService.recordStatusDir=/var/bigbluebutton/recording/status/recorded
#----------------------------------------------------
# Inject values into grails service beans
beans.presentationService.presentationDir=${presentationDir}
beans.dynamicConferenceService.recordingDir=${presentationDir}
beans.dynamicConferenceService.serviceEnabled=true
beans.dynamicConferenceService.apiVersion=0.7
beans.dynamicConferenceService.minutesElapsedBeforeMeetingExpiration=60
beans.dynamicConferenceService.securitySalt=e49e0123e531d0816abaf4bc1b1d7f11
beans.dynamicConferenceService.defaultWelcomeMessage=${defaultWelcomeMessage}
beans.dynamicConferenceService.defaultDialAccessNumber=${defaultDialAccessNumber}
beans.dynamicConferenceService.defaultMaxUsers=${defaultMaxUsers}
beans.dynamicConferenceService.defaultLogoutUrl=${bigbluebutton.web.logoutURL}
beans.dynamicConferenceService.defaultServerUrl=${bigbluebutton.web.serverURL}
beans.dynamicConferenceService.defaultNumDigitsForTelVoice=${defaultNumDigitsForTelVoice}
beans.dynamicConferenceService.defaultClientUrl=${defaultClientUrl}
beans.dynamicConferenceService.defaultMeetingDuration=${defaultMeetingDuration}

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
">
<bean id="documentConversionService" class="org.bigbluebutton.presentation.DocumentConversionServiceImp">
<property name="messagingService" ref="messagingService"/>
<property name="officeToPdfConversionService" ref="officeToPdfConversionService"/>
<property name="pdfToSwfSlidesGenerationService" ref="pdfToSwfSlidesGenerationService"/>
<property name="imageToSwfSlidesGenerationService" ref="imageToSwfSlidesGenerationService"/>
</bean>
<bean id="officeToPdfConversionService" class="org.bigbluebutton.presentation.imp.OfficeToPdfConversionService"/>
<bean id="pageExtractor" class="org.bigbluebutton.presentation.imp.GhostscriptPageExtractor">
<property name="ghostscriptExec" value="${ghostScriptExec}"/>
<property name="noPdfMarkWorkaround" value="${noPdfMarkWorkaround}"/>
</bean>
<bean id="imageMagickPageConverter" class="org.bigbluebutton.presentation.imp.ImageMagickPageConverter">
<property name="imageMagickDir" value="${imageMagickDir}"/>
</bean>
<bean id="png2SwfConverter" class="org.bigbluebutton.presentation.imp.Png2SwfPageConverter">
<property name="swfToolsDir" value="${swfToolsDir}"/>
</bean>
<bean id="jpg2SwfConverter" class="org.bigbluebutton.presentation.imp.Jpeg2SwfPageConverter">
<property name="swfToolsDir" value="${swfToolsDir}"/>
</bean>
<bean id="pageCounter" class="org.bigbluebutton.presentation.imp.Pdf2SwfPageCounter">
<property name="swfToolsDir" value="${swfToolsDir}"/>
</bean>
<bean id="pageCounterService" class="org.bigbluebutton.presentation.imp.PageCounterService">
<property name="pageCounter" ref="pageCounter"/>
<property name="maxNumPages" value="${maxNumPages}"/>
</bean>
<bean id="pdf2SwfPageConverter" class="org.bigbluebutton.presentation.imp.Pdf2SwfPageConverter">
<property name="swfToolsDir" value="${swfToolsDir}"/>
<property name="fontsDir" value="${fontsDir}"/>
</bean>
<bean id="imageConvSvc" class="org.bigbluebutton.presentation.imp.PdfPageToImageConversionService">
<property name="pageExtractor" ref="pageExtractor"/>
<property name="pdfToImageConverter" ref="imageMagickPageConverter"/>
<property name="imageToSwfConverter" ref="png2SwfConverter"/>
</bean>
<bean id="thumbCreator" class="org.bigbluebutton.presentation.imp.ThumbnailCreatorImp">
<property name="imageMagickDir" value="${imageMagickDir}"/>
<property name="blankThumbnail" value="${BLANK_THUMBNAIL}"/>
</bean>
<bean id="generatedSlidesInfoHelper" class="org.bigbluebutton.presentation.GeneratedSlidesInfoHelperImp"/>
<bean id="pdfToSwfSlidesGenerationService" class="org.bigbluebutton.presentation.imp.PdfToSwfSlidesGenerationService">
<property name="counterService" ref="pageCounterService"/>
<property name="pageConverter" ref="pdf2SwfPageConverter"/>
<property name="pdfPageToImageConversionService" ref="imageConvSvc"/>
<property name="thumbnailCreator" ref="thumbCreator"/>
<property name="blankSlide" value="${BLANK_SLIDE}"/>
<property name="maxConversionTime" value="${maxConversionTime}"/>
<property name="swfSlidesGenerationProgressNotifier" ref="swfSlidesGenerationProgressNotifier"/>
</bean>
<bean id="imageToSwfSlidesGenerationService" class="org.bigbluebutton.presentation.imp.ImageToSwfSlidesGenerationService">
<property name="pngPageConverter" ref="png2SwfConverter"/>
<property name="jpgPageConverter" ref="jpg2SwfConverter"/>
<property name="thumbnailCreator" ref="thumbCreator"/>
<property name="blankSlide" value="${BLANK_SLIDE}"/>
<property name="maxConversionTime" value="${maxConversionTime}"/>
<property name="swfSlidesGenerationProgressNotifier" ref="swfSlidesGenerationProgressNotifier"/>
</bean>
<bean id="swfSlidesGenerationProgressNotifier" class="org.bigbluebutton.presentation.imp.SwfSlidesGenerationProgressNotifier">
<property name="messagingService" ref="messagingService"/>
<property name="generatedSlidesInfoHelper" ref="generatedSlidesInfoHelper"/>
</bean>
</beans>

View File

@ -4,98 +4,48 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
">
<bean id="meetingService" class="org.bigbluebutton.api.MeetingServiceImp" >
<property name="messagingService" ref="messagingService"/>
<bean id="messagingService" class="org.bigbluebutton.api.messaging.RedisMessagingService">
<constructor-arg index="0" value="${redisHost}"/>
<constructor-arg index="1" value="${redisPort}"/>
<property name="redisPool" ref="redisPool"/>
</bean>
<bean id="paramsProcessorUtil" class="org.bigbluebutton.api.ParamsProcessorUtil"/>
<bean id="messagingService" class="org.bigbluebutton.api.messaging.RedisMessagingService">
<constructor-arg index="0" value="${redisHost}"/>
<constructor-arg index="1" value="${redisPort}"/>
<property name="redisPool" ref="redisPool"/>
</bean>
<bean id="redisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg index="0" value="${redisHost}"/>
<constructor-arg index="1" value="${redisPort}"/>
</bean>
<bean id="documentConversionService" class="org.bigbluebutton.presentation.DocumentConversionServiceImp">
<bean id="redisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg index="0" value="${redisHost}"/>
<constructor-arg index="1" value="${redisPort}"/>
</bean>
<bean id="meetingService" class="org.bigbluebutton.api.MeetingService">
<property name="defaultMeetingExpireDuration" value="${defaultMeetingExpireDuration}"/>
<property name="messagingService" ref="messagingService"/>
<property name="officeToPdfConversionService" ref="officeToPdfConversionService"/>
<property name="pdfToSwfSlidesGenerationService" ref="pdfToSwfSlidesGenerationService"/>
<property name="imageToSwfSlidesGenerationService" ref="imageToSwfSlidesGenerationService"/>
</bean>
<bean id="officeToPdfConversionService" class="org.bigbluebutton.presentation.imp.OfficeToPdfConversionService"/>
<bean id="pageExtractor" class="org.bigbluebutton.presentation.imp.GhostscriptPageExtractor">
<property name="ghostscriptExec" value="${ghostScriptExec}"/>
<property name="noPdfMarkWorkaround" value="${noPdfMarkWorkaround}"/>
</bean>
<bean id="imageMagickPageConverter" class="org.bigbluebutton.presentation.imp.ImageMagickPageConverter">
<property name="imageMagickDir" value="${imageMagickDir}"/>
</bean>
<bean id="png2SwfConverter" class="org.bigbluebutton.presentation.imp.Png2SwfPageConverter">
<property name="swfToolsDir" value="${swfToolsDir}"/>
</bean>
<bean id="jpg2SwfConverter" class="org.bigbluebutton.presentation.imp.Jpeg2SwfPageConverter">
<property name="swfToolsDir" value="${swfToolsDir}"/>
</bean>
<bean id="pageCounter" class="org.bigbluebutton.presentation.imp.Pdf2SwfPageCounter">
<property name="swfToolsDir" value="${swfToolsDir}"/>
</bean>
<bean id="pageCounterService" class="org.bigbluebutton.presentation.imp.PageCounterService">
<property name="pageCounter" ref="pageCounter"/>
<property name="maxNumPages" value="${maxNumPages}"/>
</bean>
<bean id="pdf2SwfPageConverter" class="org.bigbluebutton.presentation.imp.Pdf2SwfPageConverter">
<property name="swfToolsDir" value="${swfToolsDir}"/>
<property name="fontsDir" value="${fontsDir}"/>
</bean>
<bean id="imageConvSvc" class="org.bigbluebutton.presentation.imp.PdfPageToImageConversionService">
<property name="pageExtractor" ref="pageExtractor"/>
<property name="pdfToImageConverter" ref="imageMagickPageConverter"/>
<property name="imageToSwfConverter" ref="png2SwfConverter"/>
</bean>
<bean id="thumbCreator" class="org.bigbluebutton.presentation.imp.ThumbnailCreatorImp">
<property name="imageMagickDir" value="${imageMagickDir}"/>
<property name="blankThumbnail" value="${BLANK_THUMBNAIL}"/>
</bean>
<bean id="generatedSlidesInfoHelper" class="org.bigbluebutton.presentation.GeneratedSlidesInfoHelperImp"/>
<bean id="pdfToSwfSlidesGenerationService" class="org.bigbluebutton.presentation.imp.PdfToSwfSlidesGenerationService">
<property name="counterService" ref="pageCounterService"/>
<property name="pageConverter" ref="pdf2SwfPageConverter"/>
<property name="pdfPageToImageConversionService" ref="imageConvSvc"/>
<property name="thumbnailCreator" ref="thumbCreator"/>
<property name="blankSlide" value="${BLANK_SLIDE}"/>
<property name="maxConversionTime" value="${maxConversionTime}"/>
<property name="swfSlidesGenerationProgressNotifier" ref="swfSlidesGenerationProgressNotifier"/>
</bean>
<bean id="imageToSwfSlidesGenerationService" class="org.bigbluebutton.presentation.imp.ImageToSwfSlidesGenerationService">
<property name="pngPageConverter" ref="png2SwfConverter"/>
<property name="jpgPageConverter" ref="jpg2SwfConverter"/>
<property name="thumbnailCreator" ref="thumbCreator"/>
<property name="blankSlide" value="${BLANK_SLIDE}"/>
<property name="maxConversionTime" value="${maxConversionTime}"/>
<property name="swfSlidesGenerationProgressNotifier" ref="swfSlidesGenerationProgressNotifier"/>
</bean>
<bean id="swfSlidesGenerationProgressNotifier" class="org.bigbluebutton.presentation.imp.SwfSlidesGenerationProgressNotifier">
<property name="messagingService" ref="messagingService"/>
<property name="generatedSlidesInfoHelper" ref="generatedSlidesInfoHelper"/>
</bean>
<property name="recordingService" ref="recordingService"/>
</bean>
<bean id="recordingServiceHelper" class="org.bigbluebutton.api.RecordingServiceHelperImp"/>
<bean id="recordingService" class="org.bigbluebutton.api.RecordingService" >
<property name="recordingStatusDir" value="${recordStatusDir}"/>
<property name="publishedDir" value="${publishedDir}"/>
<property name="unpublishedDir" value="${unpublishedDir}"/>
<property name="recordingServiceHelper" ref="recordingServiceHelper"/>
</bean>
<bean id="paramsProcessorUtil" class="org.bigbluebutton.api.ParamsProcessorUtil">
<property name="apiVersion" value="${apiVersion}"/>
<property name="serviceEnabled" value="${serviceEnabled}"/>
<property name="securitySalt" value="${securitySalt}"/>
<property name="defaultMaxUsers" value="${defaultMaxUsers}"/>
<property name="defaultWelcomeMessage" value="${defaultWelcomeMessage}"/>
<property name="defaultDialAccessNumber" value="${defaultDialAccessNumber}"/>
<property name="testVoiceBridge" value="${testVoiceBridge}"/>
<property name="testConferenceMock" value="${testConferenceMock}"/>
<property name="defaultLogoutUrl" value="${bigbluebutton.web.logoutURL}"/>
<property name="defaultServerUrl" value="${bigbluebutton.web.serverURL}"/>
<property name="defaultNumDigitsForTelVoice" value="${defaultNumDigitsForTelVoice}"/>
<property name="defaultClientUrl" value="${defaultClientUrl}"/>
<property name="defaultMeetingDuration" value="${defaultMeetingDuration}"/>
</bean>
<import resource="doc-conversion.xml" />
</beans>

View File

@ -30,6 +30,7 @@ import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.bigbluebutton.web.services.DynamicConferenceService;
import org.bigbluebutton.api.domain.Meeting;
import org.bigbluebutton.api.MeetingService;
import org.bigbluebutton.api.domain.Recording;
import org.bigbluebutton.web.services.PresentationService
import org.bigbluebutton.presentation.UploadedPresentation
@ -51,7 +52,7 @@ class ApiController {
private static final String ROLE_ATTENDEE = "VIEWER";
private static final String SECURITY_SALT = '639259d4-9dd8-4b25-bf01-95f9567eaf4b'
DynamicConferenceService dynamicConferenceService;
MeetingService meetingService;
PresentationService presentationService
ParamsProcessorUtil paramsProcessorUtil
@ -64,12 +65,40 @@ class ApiController {
render(contentType:"text/xml") {
response() {
returncode(RESP_CODE_SUCCESS)
version(dynamicConferenceService.apiVersion)
version(paramsProcessorUtil.getApiVersion())
}
}
}
}
}
/**
* Substitute keywords from the welcome message.
**/
private String substituteKeywords(String message, String dialNum, String telVoice, String meetingName) {
String welcomeMessage = message
def DIAL_NUM = /%%DIALNUM%%/
def CONF_NUM = /%%CONFNUM%%/
def CONF_NAME = /%%CONFNAME%%/
def keywordList = [DIAL_NUM, CONF_NUM, CONF_NAME];
keywordList.each { keyword ->
switch(keyword){
case DIAL_NUM:
welcomeMessage = welcomeMessage.replaceAll(DIAL_NUM, dialNum)
break
case CONF_NUM:
welcomeMessage = welcomeMessage.replaceAll(CONF_NUM, telVoice)
break
case CONF_NAME:
welcomeMessage = welcomeMessage.replaceAll(CONF_NAME, meetingName)
break
}
}
return welcomeMessage;
}
/***********************************
* CREATE (API)
@ -103,7 +132,7 @@ class ApiController {
}
// Do we agree with the checksum? If not, complain.
if (! dynamicConferenceService.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
errors.checksumError()
respondWithErrors(errors)
return
@ -115,7 +144,7 @@ class ApiController {
// Get the digits for voice conference for users joining through the phone.
// If none is provided, generate one.
String telVoice = dynamicConferenceService.processTelVoice(params.voiceBridge);
String telVoice = paramsProcessorUtil.processTelVoice(params.voiceBridge);
// Get the voice conference digits/chars for users joing through VOIP on the client.
// If none is provided, make it the same as the telVoice. If one has been provided,
@ -126,16 +155,17 @@ class ApiController {
}
// Get all the other relevant parameters and generate defaults if none has been provided.
String dialNumber = dynamicConferenceService.processDialNumber(params.dialNumber);
String logoutUrl = dynamicConferenceService.processLogoutUrl(params.logoutURL);
boolean record = dynamicConferenceService.processRecordMeeting(params.record);
int maxUsers = dynamicConferenceService.processMaxUser(params.maxParticipants);
int meetingDuration = dynamicConferenceService.processMeetingDuration(params.duration);
String welcomeMessage = dynamicConferenceService.processWelcomeMessage(params.welcome == null ? "" : params.welcome, dialNumber, telVoice, meetingName);
String dialNumber = paramsProcessorUtil.processDialNumber(params.dialNumber);
String logoutUrl = paramsProcessorUtil.processLogoutUrl(params.logoutURL);
boolean record = paramsProcessorUtil.processRecordMeeting(params.record);
int maxUsers = paramsProcessorUtil.processMaxUser(params.maxParticipants);
int meetingDuration = paramsProcessorUtil.processMeetingDuration(params.duration);
String welcomeMessage = paramsProcessorUtil.processWelcomeMessage(params.welcome);
welcomeMessage = substituteKeywords(welcomeMessage, dialNumber, telVoice, meetingName);
// Translate the external meeting id into an internal meeting id.
String internalMeetingId = dynamicConferenceService.convertToInternalMeetingId(externalMeetingId);
Meeting existing = dynamicConferenceService.getMeeting(internalMeetingId);
String internalMeetingId = paramsProcessorUtil.convertToInternalMeetingId(externalMeetingId);
Meeting existing = meetingService.getMeeting(internalMeetingId);
if (existing != null) {
log.debug "Existing conference found"
if (existing.getViewerPassword().equals(viewerPass) && existing.getModeratorPassword().equals(modPass)) {
@ -152,8 +182,8 @@ class ApiController {
}
// Check if this is a test meeting. NOTE: This should not belong here. Extract this out.
if (dynamicConferenceService.isTestMeeting(telVoice)) {
digestMeetingId = dynamicConferenceService.getIntMeetingIdForTestMeeting(telVoice)
if (paramsProcessorUtil.isTestMeeting(telVoice)) {
internalMeetingId = paramsProcessorUtil.getIntMeetingIdForTestMeeting(telVoice)
}
// Collect metadata for this meeting that the third-party app wants to store if meeting is recorded.
@ -179,7 +209,7 @@ class ApiController {
.withLogoutUrl(logoutUrl).withTelVoice(telVoice).withWebVoice(webVoice).withDialNumber(dialNumber)
.withMetadata(meetingInfo).withWelcomeMessage(welcomeMessage).build()
dynamicConferenceService.createMeeting(meeting);
meetingService.createMeeting(meeting);
// See if the request came with pre-uploading of presentation.
uploadDocuments(meeting);
@ -224,7 +254,7 @@ class ApiController {
}
// Do we agree on the checksum? If not, complain.
if (! dynamicConferenceService.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
errors.checksumError()
respondWithErrors(errors)
return
@ -237,9 +267,9 @@ class ApiController {
// Everything is good so far. Translate the external meeting id to an internal meeting id. If
// we can't find the meeting, complain.
String internalMeetingId = dynamicConferenceService.convertToInternalMeetingId(externalMeetingId);
String internalMeetingId = paramsProcessorUtil.convertToInternalMeetingId(externalMeetingId);
log.info("Retrieving meeting ${internalMeetingId}")
Meeting meeting = dynamicConferenceService.getMeeting(internalMeetingId);
Meeting meeting = meetingService.getMeeting(internalMeetingId);
if (meeting == null) {
errors.invalidMeetingIdError();
respondWithErrors(errors)
@ -291,8 +321,8 @@ class ApiController {
session.setMaxInactiveInterval(SESSION_TIMEOUT);
log.info("Successfully joined. Redirecting to ${dynamicConferenceService.defaultClientUrl}");
redirect(url: dynamicConferenceService.defaultClientUrl)
log.info("Successfully joined. Redirecting to ${paramsProcessorUtil.getDefaultClientUrl()}");
redirect(url: paramsProcessorUtil.getDefaultClientUrl())
}
/*******************************************
@ -321,7 +351,7 @@ class ApiController {
}
// Do we agree on the checksum? If not, complain.
if (! dynamicConferenceService.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
errors.checksumError()
respondWithErrors(errors)
return
@ -329,9 +359,9 @@ class ApiController {
// Everything is good so far. Translate the external meeting id to an internal meeting id. If
// we can't find the meeting, complain.
String internalMeetingId = dynamicConferenceService.convertToInternalMeetingId(externalMeetingId);
String internalMeetingId = paramsProcessorUtil.convertToInternalMeetingId(externalMeetingId);
log.info("Retrieving meeting ${internalMeetingId}")
Meeting meeting = dynamicConferenceService.getMeeting(internalMeetingId);
Meeting meeting = meetingService.getMeeting(internalMeetingId);
if (meeting == null) {
errors.invalidMeetingIdError();
respondWithErrors(errors)
@ -383,7 +413,7 @@ class ApiController {
}
// Do we agree on the checksum? If not, complain.
if (! dynamicConferenceService.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
errors.checksumError()
respondWithErrors(errors)
return
@ -391,9 +421,9 @@ class ApiController {
// Everything is good so far. Translate the external meeting id to an internal meeting id. If
// we can't find the meeting, complain.
String internalMeetingId = dynamicConferenceService.convertToInternalMeetingId(externalMeetingId);
String internalMeetingId = paramsProcessorUtil.convertToInternalMeetingId(externalMeetingId);
log.info("Retrieving meeting ${internalMeetingId}")
Meeting meeting = dynamicConferenceService.getMeeting(internalMeetingId);
Meeting meeting = meetingService.getMeeting(internalMeetingId);
if (meeting == null) {
errors.invalidMeetingIdError();
respondWithErrors(errors)
@ -406,7 +436,7 @@ class ApiController {
return;
}
dynamicConferenceService.endMeetingRequest(internalMeetingId);
meetingService.endMeeting(internalMeetingId);
response.addHeader("Cache-Control", "no-cache")
withFormat {
@ -454,7 +484,7 @@ class ApiController {
}
// Do we agree on the checksum? If not, complain.
if (! dynamicConferenceService.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
errors.checksumError()
respondWithErrors(errors)
return
@ -462,9 +492,9 @@ class ApiController {
// Everything is good so far. Translate the external meeting id to an internal meeting id. If
// we can't find the meeting, complain.
String internalMeetingId = dynamicConferenceService.convertToInternalMeetingId(externalMeetingId);
String internalMeetingId = paramsProcessorUtil.convertToInternalMeetingId(externalMeetingId);
log.info("Retrieving meeting ${internalMeetingId}")
Meeting meeting = dynamicConferenceService.getMeeting(internalMeetingId);
Meeting meeting = meetingService.getMeeting(internalMeetingId);
if (meeting == null) {
errors.invalidMeetingIdError();
respondWithErrors(errors)
@ -500,13 +530,13 @@ class ApiController {
}
// Do we agree on the checksum? If not, complain.
if (! dynamicConferenceService.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
if (! paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) {
errors.checksumError()
respondWithErrors(errors)
return
}
Collection<Meeting> mtgs = dynamicConferenceService.getAllMeetings();
Collection<Meeting> mtgs = meetingService.getMeetings();
if (mtgs == null || mtgs.isEmpty()) {
response.addHeader("Cache-Control", "no-cache")
@ -610,8 +640,8 @@ class ApiController {
*************************************************/
def signOut = {
String meetingId = session["conference"]
Meeting meeting = dynamicConferenceService.getMeeting(meetingId);
String logoutUrl = dynamicConferenceService.defaultLogoutUrl
Meeting meeting = meetingService.getMeeting(meetingId);
String logoutUrl = paramsProcessorUtil.getDefaultLogoutUrl()
log.debug("Logging out from [" + meeting.getInternalId() + "]");
// Log the user out of the application.
@ -621,7 +651,7 @@ class ApiController {
logoutUrl = meeting.getLogoutUrl();
if (meeting.isRecord()) {
log.debug("[" + meeting.getInternalId() + "] is recorded. Process it.");
dynamicConferenceService.processRecording(meeting.getInternalId())
meetingService.processRecording(meeting.getInternalId())
}
} else {
log.warn("Signing out from a non-existing meeting [" + meetingId + "]");
@ -632,7 +662,7 @@ class ApiController {
}
def getRecordings = {
ArrayList<Recording> r = dynamicConferenceService.getRecordings();
ArrayList<Recording> r = meetingService.getRecordings();
if (r.isEmpty()) {
} else {
@ -721,7 +751,7 @@ class ApiController {
}
def beforeInterceptor = {
if (dynamicConferenceService.serviceEnabled == false) {
if (paramsProcessorUtil.isServiceEnabled() == false) {
log.info("apiNotEnabled: The API service and/or controller is not enabled on this server. To use it, you must first enable it.")
// TODO: this doesn't stop the request - so it generates invalid XML
// since the request continues and renders a second response

View File

@ -1,283 +0,0 @@
/* BigBlueButton - http://www.bigbluebutton.org
*
*
* Copyright (c) 2008-2009 by respective authors (see below). All rights reserved.
*
* BigBlueButton is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, If not, see <http://www.gnu.org/licenses/>.
*
* @author Jeremy Thomerson <jthomerson@genericconf.com>
* @version $Id: $
*/
package org.bigbluebutton.web.services
import java.util.concurrent.ConcurrentHashMap
import java.util.*;
import java.util.concurrent.*;
import org.bigbluebutton.api.domain.Meeting;
import org.bigbluebutton.api.domain.Recording;
import org.bigbluebutton.api.MeetingService;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
public class DynamicConferenceService {
boolean transactional = false
def serviceEnabled = false
/** See bigbluebutton.properties for default values **/
def apiVersion;
def securitySalt
int minutesElapsedBeforeMeetingExpiration = 60
int defaultMaxUsers = 20
def defaultWelcomeMessage
def defaultDialAccessNumber
def testVoiceBridge
def testConferenceMock
def recordingDir
def recordingFile
def recordStatusDir
def defaultLogoutUrl
def defaultServerUrl
def defaultNumDigitsForTelVoice
def defaultClientUrl
def defaultMeetingDuration
MeetingService meetingService
public Collection<Meeting> getAllMeetings() {
return meetingService.getMeetings()
}
public void createMeeting(Meeting meeting) {
meetingService.storeMeeting(meeting);
}
public Meeting getMeeting(String meetingId) {
return meetingService.getMeeting(meetingId);
}
public void endMeetingRequest(String meetingId) {
meetingService.endMeeting(meetingId);
}
public boolean isMeetingWithVoiceBridgeExist(String voiceBridge) {
Collection<Meeting> confs = confsByMtgID.values()
for (Meeting c : confs) {
if (voiceBridge == c.voiceBridge) {
log.debug "Found voice bridge $voiceBridge"
return true
}
}
log.debug "could not find voice bridge $voiceBridge"
return false
}
public void processRecording(String meetingId) {
System.out.println("enter processRecording " + meetingId)
Meeting room = meetingService.getMeeting(meetingId)
if (room != null) {
System.out.println("Number of participants in room " + room.getNumberOfParticipants())
if (room.getNumberOfParticipants() == 0) {
System.out.println("starting processRecording " + meetingId)
// Run conversion on another thread.
new Timer().runAfter(1000) {
startIngestAndProcessing(meetingId)
}
} else {
System.out.println("Someone still in the room...not processRecording " + meetingId)
}
} else {
System.out.println("Could not find room " + meetingId + " ... Not processing recording")
}
}
private void startIngestAndProcessing(meetingId) {
String done = recordStatusDir + "/" + meetingId + ".done"
File doneFile = new File(done)
if (!doneFile.exists()) {
doneFile.createNewFile()
if (!doneFile.exists())
log.error("Failed to create " + done + " file.")
} else {
log.error(done + " file already exists.")
}
}
public boolean isValidMeetingId(String name) {
return name ==~ /[0-9a-zA-Z_-]+/
}
public String convertToInternalMeetingId(extMeetingId) {
return DigestUtils.shaHex(extMeetingId);
}
public boolean hasChecksumAndQueryString(String checksum, String queryString) {
return (! StringUtils.isEmpty(checksum) && StringUtils.isEmpty(queryString));
}
public String processPassword(String pass) {
return StringUtils.isEmpty(pass) ? RandomStringUtils.randomAlphanumeric(8) : pass;
return paramsService.processPassword(pass);
}
public String processTelVoice(String telNum) {
return StringUtils.isEmpty(telNum) ? RandomStringUtils.randomNumeric(defaultNumDigitsForTelVoice) : telNum;
}
public String processDialNumber(String dial) {
return StringUtils.isEmpty(dial) ? defaultDialAccessNumber : dial;
}
public String processLogoutUrl(String logoutUrl) {
if (StringUtils.isEmpty(logoutUrl)) {
if (StringUtils.isEmpty(defaultLogoutUrl)) {
return defaultServerUrl;
} else {
return defaultLogoutUrl;
}
}
return logoutUrl;
}
public boolean processRecordMeeting(String record) {
boolean rec = false
if(! StringUtils.isEmpty(record)){
try {
rec = Boolean.parseBoolean(record)
} catch(Exception ex){
log.error("Failed to parse record parameter.")
rec = false;
}
}
return rec;
}
public int processMaxUser(String maxUsers) {
int mUsers = -1;
try {
mUsers = Integer.parseInt(maxUsers);
} catch(Exception ex) {
log.warn("Failed to parse maximum number of participants.");
mUsers = defaultMaxUsers;
}
return mUsers;
}
public int processMeetingDuration(String duration) {
int mDuration = -1;
try {
mDuration = Integer.parseInt(duration);
} catch(Exception ex) {
log.warn("Failed to parse meeting duration.");
mDuration = Integer.parseInt(defaultMeetingDuration);
}
return mDuration;
}
public boolean isTestMeeting(String telVoice) {
return ((! StringUtils.isEmpty(telVoice)) &&
(! StringUtils.isEmpty(testVoiceBridge)) &&
(telVoice == testVoiceBridge));
}
public String getIntMeetingIdForTestMeeting(String telVoice) {
if ((testVoiceBridge != null) && (telVoice == testVoiceBridge)) {
if (StringUtils.isEmpty(testConferenceMock))
return testConferenceMock
}
return "";
}
public Map<String, String> processMeetingInfo(HashMap<String, String> params) {
Map<String, String> meetingInfo = new HashMap<String, String>();
params.keySet().each { p ->
if (p.contains("meta")) {
String[] m = metadata.split("_")
if (m.length == 2) {
meetingInfo.put(m[1], params.get(p))
}
}
}
}
public boolean isChecksumSame(String apiCall, String checksum, String queryString) {
log.debug "checksum: " + checksum + "; query string: " + queryString
if (StringUtils.isEmpty(securitySalt)) {
log.warn "Security is disabled in this service. Make sure this is intentional."
return true;
}
// handle either checksum as first or middle / end parameter
// TODO: this is hackish - should be done better
queryString = queryString.replace("&checksum=" + checksum, "")
queryString = queryString.replace("checksum=" + checksum + "&", "")
log.debug "query string after checksum removed: " + queryString
String cs = DigestUtils.shaHex(apiCall + queryString + securitySalt);
log.debug "our checksum: " + cs
if (cs == null || cs.equals(checksum) == false) {
log.info("checksumError: request did not pass the checksum security check")
log.info("salt: ${securitySalt} checksum: ${cs} client: ${checksum} query: ${queryString}")
return false;
}
log.debug("checksum ok: request passed the checksum security check")
return true;
}
/**
* Process the welcome message parameter.
**/
public String processWelcomeMessage(String message, String dialNum, String telVoice, String meetingName) {
String welcomeMessage = message
if (StringUtils.isEmpty(message)) {
welcomeMessage = defaultWelcomeMessage
} else {
def DIAL_NUM = /%%DIALNUM%%/
def CONF_NUM = /%%CONFNUM%%/
def CONF_NAME = /%%CONFNAME%%/
def keywordList = [DIAL_NUM, CONF_NUM, CONF_NAME];
keywordList.each { keyword ->
switch(keyword){
case DIAL_NUM:
welcomeMessage = welcomeMessage.replaceAll(DIAL_NUM, dialNum)
break
case CONF_NUM:
welcomeMessage = welcomeMessage.replaceAll(CONF_NUM, telVoice)
break
case CONF_NAME:
welcomeMessage = welcomeMessage.replaceAll(CONF_NAME, meetingName)
break
}
}
}
return welcomeMessage;
}
public void setMeetingService(MeetingService s) {
meetingService = s;
}
}

View File

@ -8,9 +8,53 @@ import java.util.ArrayList;
import org.bigbluebutton.api.domain.Recording;
public class RecordingServiceHelperImp implements RecordingServiceHelper {
/*
<recording>
<id>Demo Meeting-3243244</id>
<state>available</state>
<published>true</published>
<start_time>Thu Mar 04 14:05:56 UTC 2010</start_time>
<end_time>Thu Mar 04 15:01:01 UTC 2010</end_time>
<playback>
<format>simple</format>
<link>http://server.com/simple/playback?recordingID=Demo Meeting-3243244</link>
</playback>
<meta>
<title>Test Recording 2</title>
<subject>English 232 session</subject>
<description>Second test recording</description>
<creator>Omar Shammas</creator>
<contributor>Blindside</contributor>
<language>en_US</language>
</meta>
</recording>
*/
public void writeRecordingInfo(String path, Recording info) {
def writer = new StringWriter()
def builder = new groovy.xml.MarkupBuilder(writer)
def metadataXml = builder.recording {
builder.identity(info.getId())
builder.state(info.getState())
builder.published(info.isPublished())
builder.start_time(info.getStartTime())
builder.end_time(info.getEndTime())
builder.playback {
builder.format(info.getPlaybackFormat())
builder.link(info.getPlaybackLink())
}
Map<String,String> meta = info.getMetadata();
meta.keySet().each { key ->
builder."$key"(meta.get(key))
}
}
public Recording getRecordingInfo(String id, String publishedDir, String playbackFormat) {
String path = publishedDir + File.pathSeparator + playbackFormat;
xmlEventFile = new File(path + File.pathSeparatorChar + "metadata.xml")
xmlEventFile.write writer.toString()
}
public Recording getRecordingInfo(String id, String recordingDir, String playbackFormat) {
String path = recordingDir + File.pathSeparator + playbackFormat;
File dir = new File(path);
if (dir.isDirectory()) {
def recording = new XmlSlurper().parse(new File(path + File.pathSeparatorChar + "metadata.xml"));

View File

@ -1,14 +1,181 @@
package org.bigbluebutton.api;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.*;
import org.bigbluebutton.api.domain.Meeting;
import org.bigbluebutton.api.domain.User;
import org.bigbluebutton.api.messaging.MessageListener;
import org.bigbluebutton.api.messaging.MessagingService;
import org.bigbluebutton.web.services.ExpiredMeetingCleanupTimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.lang.StringUtils;
public interface MeetingService {
public void removeExpiredMeetings();
public Collection<Meeting> getMeetings();
public void storeMeeting(Meeting m);
public void endMeeting(String meetingId);
public Meeting getMeeting(String meetingId);
public boolean isMeetingWithVoiceBridgeExist(String voiceBridge);
void send(String channel, String message);
}
public class MeetingService {
private static Logger log = LoggerFactory.getLogger(MeetingService.class);
private final ConcurrentMap<String, Meeting> meetings;
private int defaultMeetingExpireDuration = 60;
private RecordingService recordingService;
private MessagingService messagingService;
private ExpiredMeetingCleanupTimerTask cleaner;
public MeetingService() {
meetings = new ConcurrentHashMap<String, Meeting>();
}
/**
* Remove the meetings that have ended from the list of
* running meetings.
*/
// @Override
public void removeExpiredMeetings() {
for (Meeting m : meetings.values()) {
if (m.hasExpired(defaultMeetingExpireDuration) || m.wasNeverStarted(defaultMeetingExpireDuration)) {
log.info("Removing expired meeting [{} - {}]", m.getInternalId(), m.getName());
meetings.remove(m.getInternalId());
continue;
}
if (m.hasExceededDuration()) {
log.info("Ending meeting [{} - {}]", m.getInternalId(), m.getName());
m.setForciblyEnded(true);
endMeeting(m.getInternalId());
}
}
}
public Collection<Meeting> getMeetings() {
log.debug("The number of meetings are: " + meetings.size());
return meetings.isEmpty() ? Collections.<Meeting>emptySet() : Collections.unmodifiableCollection(meetings.values());
}
public void createMeeting(Meeting m) {
log.debug("Storing Meeting with internal id:" + m.getInternalId());
meetings.put(m.getInternalId(), m);
}
public Meeting getMeeting(String meetingId) {
if (StringUtils.isEmpty(meetingId)) {
return null;
}
for (String key : meetings.keySet()) {
if (key.startsWith(meetingId))
return (Meeting) meetings.get(key);
}
return null;
}
public void processRecording(String meetingId) {
log.debug("Checking if we need to process recording for [{}]", meetingId);
Meeting m = getMeeting(meetingId);
if (m != null) {
int numUsers = m.getNumUsers();
if (numUsers == 0) {
recordingService.startIngestAndProcessing(meetingId);
} else {
log.debug("Meeting [{}] is not empty with {} users.", meetingId, numUsers);
}
} else {
log.warn("Meeting [{}] does not exist.", meetingId);
}
}
public boolean isMeetingWithVoiceBridgeExist(String voiceBridge) {
/* Collection<Meeting> confs = meetings.values();
for (Meeting c : confs) {
if (voiceBridge == c.getVoiceBridge()) {
return true;
}
}
*/ return false;
}
public void send(String channel, String message) {
messagingService.send(channel, message);
}
public void endMeeting(String meetingId) {
messagingService.endMeeting(meetingId);
}
public void setDefaultMeetingExpireDuration(int meetingExpiration) {
this.defaultMeetingExpireDuration = meetingExpiration;
}
public void setRecordingService(RecordingService s) {
recordingService = s;
}
public void setMessagingService(MessagingService mess) {
messagingService = mess;
messagingService.addListener(new MeetingMessageListener());
messagingService.start();
}
public void setExpiredMeetingCleanupTimerTask(ExpiredMeetingCleanupTimerTask c) {
cleaner = c;
cleaner.setMeetingService(this);
cleaner.start();
}
/**
* Class that listens for messages from bbb-apps.
* @author Richard Alam
*
*/
private class MeetingMessageListener implements MessageListener {
@Override
public void meetingStarted(String meetingId) {
Meeting m = getMeeting(meetingId);
if (m != null) {
m.setStartTime(System.currentTimeMillis());
log.debug("Setting meeting started time");
}
}
@Override
public void meetingEnded(String meetingId) {
Meeting m = getMeeting(meetingId);
if (m != null) {
m.setEndTime(System.currentTimeMillis());
log.debug("Setting meeting end time");
}
}
@Override
public void userJoined(String meetingId, String userId, String name, String role) {
Meeting m = getMeeting(meetingId);
if (m != null) {
User user = new User(userId, name, role);
m.userJoined(user);
log.debug("New user in meeting:"+user.getFullname());
}
}
@Override
public void userLeft(String meetingId, String userId) {
Meeting m = getMeeting(meetingId);
if (m != null) {
User user = m.userLeft(userId);
log.debug("User removed from meeting:" + user.getFullname());
}
}
@Override
public void updatedStatus(String meetingId, String userId, String status, String value) {
Meeting m = getMeeting(meetingId);
if (m != null) {
User user = m.getUserById(userId);
user.setStatus(status, value);
log.debug("Setting new status value for participant:"+user.getFullname());
}
}
}
}

View File

@ -1,171 +0,0 @@
package org.bigbluebutton.api;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.*;
import org.bigbluebutton.api.domain.Meeting;
import org.bigbluebutton.api.domain.User;
import org.bigbluebutton.api.messaging.MessageListener;
import org.bigbluebutton.api.messaging.MessagingService;
import org.bigbluebutton.web.services.ExpiredMeetingCleanupTimerTask;
import org.bigbluebutton.web.services.IDynamicConferenceService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.lang.StringUtils;
public class MeetingServiceImp implements MeetingService {
private static Logger log = LoggerFactory.getLogger(MeetingServiceImp.class);
private final ConcurrentMap<String, Meeting> meetings;
private int minutesElapsedBeforeMeetingExpiration = 60;
private IDynamicConferenceService dynConfService;
private MessagingService messagingService;
private ExpiredMeetingCleanupTimerTask cleaner;
public MeetingServiceImp() {
meetings = new ConcurrentHashMap<String, Meeting>();
}
/**
* Remove the meetings that have ended from the list of
* running meetings.
*/
@Override
public void removeExpiredMeetings() {
for (Meeting m : meetings.values()) {
if (m.hasExpired(minutesElapsedBeforeMeetingExpiration) || m.wasNeverStarted(minutesElapsedBeforeMeetingExpiration)) {
log.info("Removing expired meeting [{} - {}]", m.getInternalId(), m.getName());
meetings.remove(m.getInternalId());
continue;
}
if (m.hasExceededDuration()) {
log.info("Ending meeting [{} - {}]", m.getInternalId(), m.getName());
m.setForciblyEnded(true);
endMeeting(m.getInternalId());
}
}
}
public Collection<Meeting> getMeetings() {
log.debug("The number of meetings are: " + meetings.size());
return meetings.isEmpty() ? Collections.<Meeting>emptySet() : Collections.unmodifiableCollection(meetings.values());
}
public void storeMeeting(Meeting m) {
log.debug("Storing Meeting with internal id:" + m.getInternalId());
meetings.put(m.getInternalId(), m);
}
public Meeting getMeeting(String meetingId) {
if (StringUtils.isEmpty(meetingId)) {
return null;
}
for (String key : meetings.keySet()) {
if (key.startsWith(meetingId))
return (Meeting) meetings.get(key);
}
return null;
}
public boolean isMeetingWithVoiceBridgeExist(String voiceBridge) {
/* Collection<Meeting> confs = meetings.values();
for (Meeting c : confs) {
if (voiceBridge == c.getVoiceBridge()) {
return true;
}
}
*/ return false;
}
public void send(String channel, String message) {
messagingService.send(channel, message);
}
public void endMeeting(String meetingId) {
messagingService.endMeeting(meetingId);
}
public void setMinutesElapsedBeforeMeetingExpiration(int minutes) {
minutesElapsedBeforeMeetingExpiration = minutes;
}
public void setDynamicConferenceService(IDynamicConferenceService s) {
dynConfService = s;
s.setMeetingService((MeetingService) this);
}
public void setMessagingService(MessagingService mess) {
messagingService = mess;
messagingService.addListener(new MeetingMessageListener());
messagingService.start();
}
public void setExpiredMeetingCleanupTimerTask(ExpiredMeetingCleanupTimerTask c) {
cleaner = c;
cleaner.setMeetingService(this);
cleaner.start();
}
/**
* Class that listens for messages from bbb-apps.
* @author Richard Alam
*
*/
private class MeetingMessageListener implements MessageListener {
@Override
public void meetingStarted(String meetingId) {
Meeting m = getMeeting(meetingId);
if (m != null) {
m.setStartTime(System.currentTimeMillis());
log.debug("Setting meeting started time");
}
}
@Override
public void meetingEnded(String meetingId) {
Meeting m = getMeeting(meetingId);
if (m != null) {
m.setEndTime(System.currentTimeMillis());
log.debug("Setting meeting end time");
}
}
@Override
public void userJoined(String meetingId, String userId, String name, String role) {
Meeting m = getMeeting(meetingId);
if (m != null) {
User user = new User(userId, name, role);
m.userJoined(user);
log.debug("New user in meeting:"+user.getFullname());
}
}
@Override
public void userLeft(String meetingId, String userId) {
Meeting m = getMeeting(meetingId);
if (m != null) {
User user = m.userLeft(userId);
log.debug("User removed from meeting:" + user.getFullname());
}
}
@Override
public void updatedStatus(String meetingId, String userId, String status, String value) {
Meeting m = getMeeting(meetingId);
if (m != null) {
User user = m.getUserById(userId);
user.setStatus(status, value);
log.debug("Setting new status value for participant:"+user.getFullname());
}
}
}
}

View File

@ -1,25 +1,51 @@
package org.bigbluebutton.api;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ParamsProcessorUtil {
private static Logger log = LoggerFactory.getLogger(ParamsProcessorUtil.class);
private String apiVersion;
private boolean serviceEnabled = false;
private String securitySalt;
private int minutesElapsedBeforeMeetingExpiration = 60;
private int defaultMaxUsers = 20;
private String defaultWelcomeMessage;
private String defaultDialAccessNumber;
private String testVoiceBridge;
private String testConferenceMock;
private String recordingDir;
private String recordingFile;
private String recordStatusDir;
private String defaultLogoutUrl;
private String defaultServerUrl;
private String defaultNumDigitsForTelVoice;
private int defaultNumDigitsForTelVoice;
private String defaultClientUrl;
private String defaultMeetingDuration;
private int defaultMeetingDuration;
public String getApiVersion() {
return apiVersion;
}
public boolean isServiceEnabled() {
return serviceEnabled;
}
public String getDefaultClientUrl() {
return defaultClientUrl;
}
public String processWelcomeMessage(String message) {
String welcomeMessage = message;
if (StringUtils.isEmpty(message)) {
welcomeMessage = defaultWelcomeMessage;
}
return welcomeMessage;
}
public String convertToInternalMeetingId(String extMeetingId) {
return DigestUtils.shaHex(extMeetingId);
}
public String processPassword(String pass) {
return StringUtils.isEmpty(pass) ? RandomStringUtils.randomAlphanumeric(8) : pass;
@ -30,7 +56,7 @@ public class ParamsProcessorUtil {
}
public String processTelVoice(String telNum) {
return StringUtils.isEmpty(telNum) ? RandomStringUtils.randomNumeric(Integer.parseInt(defaultNumDigitsForTelVoice)) : telNum;
return StringUtils.isEmpty(telNum) ? RandomStringUtils.randomNumeric(defaultNumDigitsForTelVoice) : telNum;
}
public String processDialNumber(String dial) {
@ -80,7 +106,7 @@ public class ParamsProcessorUtil {
try {
mDuration = Integer.parseInt(duration);
} catch(Exception ex) {
mDuration = Integer.parseInt(defaultMeetingDuration);
mDuration = defaultMeetingDuration;
}
return mDuration;
@ -101,6 +127,30 @@ public class ParamsProcessorUtil {
return "";
}
public boolean isChecksumSame(String apiCall, String checksum, String queryString) {
log.debug("checksum: [{}] ; query string: [{}]", checksum, queryString);
if (StringUtils.isEmpty(securitySalt)) {
log.warn("Security is disabled in this service. Make sure this is intentional.");
return true;
}
// handle either checksum as first or middle / end parameter
// TODO: this is hackish - should be done better
queryString = queryString.replace("&checksum=" + checksum, "");
queryString = queryString.replace("checksum=" + checksum + "&", "");
log.debug("query string after checksum removed: [{}]", queryString);
String cs = DigestUtils.shaHex(apiCall + queryString + securitySalt);
log.debug("our checksum: [{}], client: [{}]", cs, checksum);
if (cs == null || cs.equals(checksum) == false) {
log.info("checksumError: request did not pass the checksum security check");
return false;
}
log.debug("checksum ok: request passed the checksum security check");
return true;
}
/*************************************************
* Setters
************************************************/
@ -109,15 +159,14 @@ public class ParamsProcessorUtil {
this.apiVersion = apiVersion;
}
public void setServiceEnabled(boolean e) {
serviceEnabled = e;
}
public void setSecuritySalt(String securitySalt) {
this.securitySalt = securitySalt;
}
public void setMinutesElapsedBeforeMeetingExpiration(
int minutesElapsedBeforeMeetingExpiration) {
this.minutesElapsedBeforeMeetingExpiration = minutesElapsedBeforeMeetingExpiration;
}
public void setDefaultMaxUsers(int defaultMaxUsers) {
this.defaultMaxUsers = defaultMaxUsers;
}
@ -138,18 +187,6 @@ public class ParamsProcessorUtil {
this.testConferenceMock = testConferenceMock;
}
public void setRecordingDir(String recordingDir) {
this.recordingDir = recordingDir;
}
public void setRecordingFile(String recordingFile) {
this.recordingFile = recordingFile;
}
public void setRecordStatusDir(String recordStatusDir) {
this.recordStatusDir = recordStatusDir;
}
public void setDefaultLogoutUrl(String defaultLogoutUrl) {
this.defaultLogoutUrl = defaultLogoutUrl;
}
@ -158,7 +195,7 @@ public class ParamsProcessorUtil {
this.defaultServerUrl = defaultServerUrl;
}
public void setDefaultNumDigitsForTelVoice(String defaultNumDigitsForTelVoice) {
public void setDefaultNumDigitsForTelVoice(int defaultNumDigitsForTelVoice) {
this.defaultNumDigitsForTelVoice = defaultNumDigitsForTelVoice;
}
@ -166,7 +203,7 @@ public class ParamsProcessorUtil {
this.defaultClientUrl = defaultClientUrl;
}
public void setDefaultMeetingDuration(String defaultMeetingDuration) {
public void setDefaultMeetingDuration(int defaultMeetingDuration) {
this.defaultMeetingDuration = defaultMeetingDuration;
}
}

View File

@ -2,14 +2,37 @@ package org.bigbluebutton.api;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import org.bigbluebutton.api.domain.Recording;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RecordingService {
private static Logger log = LoggerFactory.getLogger(RecordingService.class);
private String publishedDir = "/var/bigbluebutton/published";
private String unpublishedDir = "/var/bigbluebutton/unpublished";
private RecordingServiceHelper recordingServiceHelper;
private String recordStatusDir;
public void startIngestAndProcessing(String meetingId) {
String done = recordStatusDir + "/" + meetingId + ".done";
File doneFile = new File(done);
if (!doneFile.exists()) {
try {
doneFile.createNewFile();
if (!doneFile.exists())
log.error("Failed to create " + done + " file.");
} catch (IOException e) {
log.error("Failed to create " + done + " file.");
}
} else {
log.error(done + " file already exists.");
}
}
public ArrayList<Recording> getRecordings(String meetingId) {
ArrayList<Recording> recs = new ArrayList<Recording>();
@ -32,29 +55,89 @@ public class RecordingService {
String[] format = getPlaybackFormats(path);
for (int i = 0; i < format.length; i++) {
File[] recordings = getDirectories(publishedDir + File.pathSeparatorChar + format[i]);
File[] recordings = getDirectories(path + File.pathSeparatorChar + format[i]);
for (int f = 0; f < recordings.length; f++) {
if (recordings[f].getName().startsWith(meetingId)) {
Recording r = getRecordingInfo(recordings[f].getName(), format[i]);
Recording r = getRecordingInfo(path, recordings[f].getName(), format[i]);
if (r != null) recs.add(r);
}
}
}
}
return recs;
}
public Recording getRecordingInfo(String recordingId, String format) {
Recording rec = recordingServiceHelper.getRecordingInfo(recordingId, publishedDir, format);
return getRecordingInfo(publishedDir, recordingId, format);
}
private Recording getRecordingInfo(String path, String recordingId, String format) {
Recording rec = recordingServiceHelper.getRecordingInfo(recordingId, path, format);
return rec;
}
public void publish(String recordingId) {
public void publish(String recordingId, boolean publish) {
publish(publishedDir, recordingId, publish);
publish(unpublishedDir, recordingId, publish);
}
public void delete(String recordingId) {
private void publish(String path, String recordingId, boolean publish) {
String[] format = getPlaybackFormats(path);
for (int i = 0; i < format.length; i++) {
File[] recordings = getDirectories(path + File.pathSeparatorChar + format[i]);
for (int f = 0; f < recordings.length; f++) {
if (recordings[f].getName().equals(recordingId)) {
Recording r = getRecordingInfo(recordingId, path, format[i]);
if (r != null) {
File dest;
if (publish) {
dest = new File(publishedDir);
} else {
dest = new File(unpublishedDir);
}
boolean moved = recordings[f].renameTo(new File(dest, recordings[f].getName()));
if (moved) {
r.setPublished(publish);
recordingServiceHelper.writeRecordingInfo(dest.getAbsolutePath() + File.pathSeparatorChar + recordings[f].getName(), r);
}
}
}
}
}
}
public void delete(String recordingId) {
deleteRecording(recordingId, publishedDir);
deleteRecording(recordingId, unpublishedDir);
}
private void deleteRecording(String id, String path) {
String[] format = getPlaybackFormats(path);
for (int i = 0; i < format.length; i++) {
File[] recordings = getDirectories(path + File.pathSeparatorChar + format[i]);
for (int f = 0; f < recordings.length; f++) {
if (recordings[f].getName().equals(id)) {
deleteDirectory(recordings[f]);
}
}
}
}
private void deleteDirectory(File directory) {
/**
* Go through each directory and check if it's not empty.
* We need to delete files inside a directory before a
* directory can be deleted.
**/
File[] files = directory.listFiles();
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
deleteDirectory(files[i]);
} else {
files[i].delete();
}
}
// Now that the directory is empty. Delete it.
directory.delete();
}
private File[] getDirectories(String path) {
@ -77,6 +160,14 @@ public class RecordingService {
return formats;
}
public void setRecordingStatusDir(String dir) {
recordStatusDir = dir;
}
public void setUnpublishedDir(String dir) {
unpublishedDir = dir;
}
public void setPublishedDir(String dir) {
publishedDir = dir;
}

View File

@ -4,4 +4,5 @@ import org.bigbluebutton.api.domain.Recording;
public interface RecordingServiceHelper {
public Recording getRecordingInfo(String id, String publishedDir, String playbackFormat);
public void writeRecordingInfo(String path, Recording info);
}

View File

@ -62,7 +62,11 @@ public class RedisMessagingService implements MessagingService {
}
public void endMeeting(String meetingId) {
log.warn("***Need to implement sending of end meeting request!");
HashMap<String,String> map = new HashMap<String, String>();
map.put("request", "endMeeting");
map.put("meetingId", meetingId);
Gson gson = new Gson();
send(MessagingConstants.SYSTEM_CHANNEL, gson.toJson(map));
}
public void send(String channel, String message) {

View File

@ -1,7 +1,9 @@
package org.bigbluebutton.web.controllers
import grails.test.*
import org.bigbluebutton.web.services.DynamicConferenceService;
import org.bigbluebutton.api.ParamsProcessorUtil;
import org.bigbluebutton.api.RecordingService;
import org.apache.commons.codec.digest.DigestUtils;
import org.bigbluebutton.api.*;
import org.bigbluebutton.api.domain.Meeting;
@ -10,31 +12,51 @@ import org.bigbluebutton.api.messaging.NullMessagingService;
import org.bigbluebutton.api.messaging.MessagingService;
class ApiControllerTests extends ControllerUnitTestCase {
String API_VERSION = "0.7"
String SALT = 'ab56fda9fc1c2bde2d65ff76134b47ad'
final String API_VERSION = "0.7"
final boolean SERVICE_ENABLED = true;
final String SALT = 'ab56fda9fc1c2bde2d65ff76134b47ad'
final int DEF_MAX_USERS = 20
final String DEF_WELCOME_MSG = "<br>Welcome to this BigBlueButton Demo Server.<br><br>For help using BigBlueButton <a href=\"event:http://www.bigbluebutton.org/content/videos\"><u>check out these videos</u></a>.<br><br>"
final String DEF_DIAL_NUMER = "613-555-1234"
final String TEST_VOICE_BRIDGE = "9999"
final String TEST_CONF_MOCK = "conference-mock-default"
final String DEF_LOGOUT_URL = ""
final String DEF_SERVER_URL = "http://192.168.0.166"
final int DEF_NUM_FOR_VOICE = 5;
final String DEF_CLIENT_URL = "http://localhost/client/BigBlueButton.html"
final int DEF_MEETING_DURATION = 210;
String MEETING_ID = "default-meeting-id"
String MEETING_NAME = "Default Meeting"
String MOD_PASS = "modpass"
String VIEW_PASS = "viewpass"
String CLIENT_URL = "http://localhost/client/BigBlueButton.html"
long CREATE_TIME = 1234567890
def dynamicConferenceService
MeetingServiceImp meetingService
RecordingService recordingService
MeetingService meetingService
ParamsProcessorUtil ppu = new ParamsProcessorUtil();
protected void setUp() {
super.setUp()
mockLogging(DynamicConferenceService)
dynamicConferenceService = new DynamicConferenceService()
meetingService = new MeetingServiceImp()
mockLogging(RecordingService)
recordingService = new RecordingService()
meetingService = new MeetingService()
MessagingService ms = new NullMessagingService();
meetingService.setMessagingService(ms);
dynamicConferenceService.setMeetingService(meetingService)
dynamicConferenceService.apiVersion = API_VERSION
dynamicConferenceService.securitySalt = SALT
dynamicConferenceService.defaultNumDigitsForTelVoice = 5
dynamicConferenceService.defaultNumDigitsForTelVoice = 5
dynamicConferenceService.defaultClientUrl = CLIENT_URL
ppu.setApiVersion(API_VERSION)
ppu.setSecuritySalt(SALT);
ppu.setDefaultNumDigitsForTelVoice(5)
ppu.setDefaultClientUrl(DEF_CLIENT_URL)
ppu.setDefaultMeetingDuration(DEF_MEETING_DURATION);
ppu.setDefaultDialAccessNumber(DEF_DIAL_NUMER);
ppu.setDefaultMaxUsers(DEF_MAX_USERS);
ppu.setDefaultLogoutUrl(DEF_LOGOUT_URL);
ppu.setDefaultWelcomeMessage(DEF_WELCOME_MSG);
ppu.setServiceEnabled(SERVICE_ENABLED);
ppu.setTestConferenceMock(TEST_CONF_MOCK);
ppu.setTestVoiceBridge(VIEW_PASS);
}
protected void tearDown() {
@ -44,7 +66,8 @@ class ApiControllerTests extends ControllerUnitTestCase {
void testIndex() {
ApiController controller = new ApiController()
mockLogging(ApiController)
controller.setDynamicConferenceService(dynamicConferenceService)
controller.setMeetingService(meetingService)
controller.setParamsProcessorUtil(ppu);
controller.index()
println "controller response = " + controller.response.contentAsString
}
@ -52,7 +75,8 @@ class ApiControllerTests extends ControllerUnitTestCase {
void testCreateAPI() {
ApiController controller = new ApiController()
mockLogging(ApiController)
controller.setDynamicConferenceService(dynamicConferenceService)
controller.setMeetingService(meetingService)
controller.setParamsProcessorUtil(ppu);
createMeeting(controller)
controller.create()
@ -63,46 +87,46 @@ class ApiControllerTests extends ControllerUnitTestCase {
/**
* Now that the meeting has been setup. Try to join it.
*/
ApiController controller2 = new ApiController()
ApiController controller = new ApiController()
mockLogging(ApiController)
controller2.setDynamicConferenceService(dynamicConferenceService)
controller.setMeetingService(meetingService)
controller.setParamsProcessorUtil(ppu);
// Create a conference. This prevents us from calling the CREATE API
dynamicConferenceService.createMeeting(createDefaultMeeting())
meetingService.createMeeting(createDefaultMeeting())
joinMeeting(controller2)
controller2.join()
println "controller response = " + controller2.response.contentAsString
joinMeeting(controller)
controller.join()
println "controller response = " + controller.response.contentAsString
/**
* Need to use controller2.redirectArgs['url'] instead of controller2.response.redirectedUrl as
* shown in the grails doc because it is returning null for me.
*
* see http://kousenit.wordpress.com/2010/11/10/unit-testing-grails-controllers-revisited/
*/
assertEquals CLIENT_URL, controller2.redirectArgs['url']
assertEquals DEF_CLIENT_URL, controller.redirectArgs['url']
}
void testIsMeetingRunningAPI() {
ApiController controller3 = new ApiController()
ApiController controller = new ApiController()
mockLogging(ApiController)
controller3.setDynamicConferenceService(dynamicConferenceService)
controller.setMeetingService(meetingService)
controller.setParamsProcessorUtil(ppu);
// Create a conference. This prevents us from calling the CREATE API
dynamicConferenceService.createMeeting(createDefaultMeeting())
meetingService.createMeeting(createDefaultMeeting())
isMeetingRunning(controller3)
controller3.isMeetingRunning()
println "controller response = " + controller3.response.contentAsString
isMeetingRunning(controller)
controller.isMeetingRunning()
println "controller response = " + controller.response.contentAsString
}
void testEndAPI() {
ApiController endCtlr = new ApiController()
mockLogging(ApiController)
endCtlr.setDynamicConferenceService(dynamicConferenceService)
endCtlr.setMeetingService(meetingService)
endCtlr.setParamsProcessorUtil(ppu);
// Create a conference. This prevents us from calling the CREATE API
dynamicConferenceService.createMeeting(createDefaultMeeting())
meetingService.createMeeting(createDefaultMeeting())
endMeeting(endCtlr)
endCtlr.end()
@ -112,14 +136,14 @@ class ApiControllerTests extends ControllerUnitTestCase {
void testGetMeetingInfo() {
ApiController gmiCtlr = new ApiController()
mockLogging(ApiController)
gmiCtlr.setDynamicConferenceService(dynamicConferenceService)
gmiCtlr.setMeetingService(meetingService)
gmiCtlr.setParamsProcessorUtil(ppu);
// Create a conference. This prevents us from calling the CREATE API
Meeting m = createDefaultMeeting()
// Add a user.
User u = new User("test-user", "Test User", "MODERATOR");
m.userJoined(u);
dynamicConferenceService.createMeeting(m)
meetingService.createMeeting(m)
getMeetingInfo(gmiCtlr)
gmiCtlr.getMeetingInfo()
@ -129,17 +153,17 @@ class ApiControllerTests extends ControllerUnitTestCase {
void testGetMeetings() {
ApiController gmCtlr = new ApiController()
mockLogging(ApiController)
gmCtlr.setDynamicConferenceService(dynamicConferenceService)
gmCtlr.setMeetingService(meetingService)
gmCtlr.setParamsProcessorUtil(ppu);
// Create a conference. This prevents us from calling the CREATE API
Meeting m = createDefaultMeeting()
// Add a user.
User u = new User("test-user", "Test User", "MODERATOR");
m.userJoined(u);
dynamicConferenceService.createMeeting(m)
meetingService.createMeeting(m)
// Create another meeting
dynamicConferenceService.createMeeting(createAnotherMeeting());
meetingService.createMeeting(createAnotherMeeting());
getMeetings(gmCtlr)
gmCtlr.getMeetings()
@ -153,7 +177,7 @@ class ApiControllerTests extends ControllerUnitTestCase {
private Meeting createDefaultMeeting() {
String internalMeetingId = dynamicConferenceService.convertToInternalMeetingId(MEETING_ID)
String internalMeetingId = ppu.convertToInternalMeetingId(MEETING_ID)
String logoutUrl = "http://localhost"
String telVoice = "85115"
String webVoice = "bbb-85115"
@ -178,7 +202,7 @@ class ApiControllerTests extends ControllerUnitTestCase {
private Meeting createAnotherMeeting() {
String externalMeetingId = "cook-with-omar"
String internalMeetingId = dynamicConferenceService.convertToInternalMeetingId(externalMeetingId)
String internalMeetingId = ppu.convertToInternalMeetingId(externalMeetingId)
String logoutUrl = "http://localhost"
String telVoice = "85116"
String webVoice = "bbb-85116"

View File

@ -1,46 +0,0 @@
package org.bigbluebutton.web.services;
import groovy.util.GroovyTestCase;
class DynamicConferenceServiceTests extends GroovyTestCase {
protected void setUp() {
super.setUp()
}
protected void tearDown() {
super.tearDown()
}
void testIsValidMeetingId() {
DynamicConferenceService service = new DynamicConferenceService()
assertTrue(service.isValidMeetingId("test-meeting"))
assertFalse(service.isValidMeetingId("test meeting"))
assertFalse(service.isValidMeetingId("test.meeting-valid name"))
}
void testProcessMeetingInfo() {
Map params = new LinkedHashMap()
params.put("ext_sakai_server", "http://192.168.0.60:8080");
params.put("oauth_nonce", "2936012982701750");
params.put("oauth_consumer_key", "fred");
params.put("context_label", "BasicLTI Test");
}
}
/*
DynamicConference conf = new DynamicConference("Test Conf", "abc", "123", "456", 30);
service.storeConference(conf);
assertEquals(conf, service.getConferenceByMeetingID("abc"));
assertNull(service.getConferenceByMeetingID("abd"));
assertNull(service.getConferenceByMeetingID(conf.getMeetingToken()));
assertNull(service.getConferenceByMeetingID("abcd"));
assertEquals(conf, service.getConferenceByMeetingID("abc"));
*/